agentengine-sdk-python 0.6.4__py3-none-any.whl → 0.7.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 (486) hide show
  1. agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
  2. agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
  3. {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
  7. ksadk/__init__.py +13 -0
  8. ksadk/__main__.py +4 -0
  9. ksadk/a2a/__init__.py +15 -0
  10. ksadk/a2a/card_builder.py +64 -0
  11. ksadk/a2a/client.py +309 -0
  12. ksadk/a2a/executor.py +201 -0
  13. ksadk/a2a/server.py +79 -0
  14. ksadk/agents/__init__.py +20 -0
  15. ksadk/agents/base.py +370 -0
  16. ksadk/agents/context.py +38 -0
  17. ksadk/agents/event.py +35 -0
  18. ksadk/agents/loop_agent.py +71 -0
  19. ksadk/agents/parallel_agent.py +109 -0
  20. ksadk/agents/runner_adapter.py +24 -0
  21. ksadk/agents/sequential_agent.py +34 -0
  22. ksadk/api/__init__.py +3 -0
  23. ksadk/api/client.py +2246 -0
  24. ksadk/builders/__init__.py +20 -0
  25. ksadk/builders/base.py +66 -0
  26. ksadk/builders/code_builder.py +1990 -0
  27. ksadk/builders/container_builder.py +786 -0
  28. ksadk/builders/framework_requirements.py +59 -0
  29. ksadk/builders/ks3_uploader.py +389 -0
  30. ksadk/builders/mcp_builder.py +476 -0
  31. ksadk/builders/requirements_utils.py +75 -0
  32. ksadk/cli/__init__.py +531 -0
  33. ksadk/cli/__main__.py +4 -0
  34. ksadk/cli/agent_ref.py +204 -0
  35. ksadk/cli/cmd_a2a.py +167 -0
  36. ksadk/cli/cmd_agent.py +201 -0
  37. ksadk/cli/cmd_build.py +270 -0
  38. ksadk/cli/cmd_completion.py +304 -0
  39. ksadk/cli/cmd_config.py +758 -0
  40. ksadk/cli/cmd_create.py +2304 -0
  41. ksadk/cli/cmd_dashboard.py +900 -0
  42. ksadk/cli/cmd_deploy.py +704 -0
  43. ksadk/cli/cmd_destroy.py +345 -0
  44. ksadk/cli/cmd_files.py +1227 -0
  45. ksadk/cli/cmd_hermes.py +1263 -0
  46. ksadk/cli/cmd_invoke.py +1503 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1179 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3922 -0
  51. ksadk/cli/cmd_run.py +239 -0
  52. ksadk/cli/cmd_status.py +588 -0
  53. ksadk/cli/cmd_version.py +522 -0
  54. ksadk/cli/cmd_web.py +288 -0
  55. ksadk/cli/deploy_utils.py +87 -0
  56. ksadk/cli/dry_run.py +214 -0
  57. ksadk/cli/env_options.py +82 -0
  58. ksadk/cli/error_utils.py +525 -0
  59. ksadk/cli/global_options.py +31 -0
  60. ksadk/cli/local_runtime.py +97 -0
  61. ksadk/cli/model_catalog.py +136 -0
  62. ksadk/cli/network_options.py +412 -0
  63. ksadk/cli/resource_common.py +621 -0
  64. ksadk/cli/storage.py +84 -0
  65. ksadk/cli/ui.py +431 -0
  66. ksadk/cli/workflow_common.py +552 -0
  67. ksadk/common/__init__.py +14 -0
  68. ksadk/common/aicp_env.py +128 -0
  69. ksadk/common/auth.py +190 -0
  70. ksadk/common/constants.py +84 -0
  71. ksadk/common/llm_utils.py +61 -0
  72. ksadk/configs/__init__.py +61 -0
  73. ksadk/configs/env_registry.py +343 -0
  74. ksadk/configs/global_config.py +200 -0
  75. ksadk/configs/settings.py +706 -0
  76. ksadk/conversations/__init__.py +95 -0
  77. ksadk/conversations/attachment_storage.py +379 -0
  78. ksadk/conversations/attachments.py +774 -0
  79. ksadk/conversations/compaction_pipeline.py +387 -0
  80. ksadk/conversations/compaction_prompt.py +128 -0
  81. ksadk/conversations/context.py +303 -0
  82. ksadk/conversations/message_projection.py +269 -0
  83. ksadk/conversations/model_context.py +370 -0
  84. ksadk/conversations/model_options.py +136 -0
  85. ksadk/conversations/normalize.py +446 -0
  86. ksadk/conversations/reasoning_markup.py +91 -0
  87. ksadk/conversations/run_kinds.py +61 -0
  88. ksadk/conversations/run_status.py +53 -0
  89. ksadk/conversations/runtime.py +5276 -0
  90. ksadk/conversations/semantic_summary.py +359 -0
  91. ksadk/conversations/session_title.py +254 -0
  92. ksadk/deployment/__init__.py +83 -0
  93. ksadk/deployment/agent_access.py +226 -0
  94. ksadk/deployment/base.py +229 -0
  95. ksadk/deployment/manager.py +332 -0
  96. ksadk/deployment/providers/__init__.py +10 -0
  97. ksadk/deployment/providers/serverless.py +1115 -0
  98. ksadk/deployment/registry.py +120 -0
  99. ksadk/deployment/state.py +78 -0
  100. ksadk/deployment/ui_config.py +21 -0
  101. ksadk/detection/__init__.py +7 -0
  102. ksadk/detection/detector.py +431 -0
  103. ksadk/detection/mcp_detector.py +250 -0
  104. ksadk/hermes_terminal.py +467 -0
  105. ksadk/identity/__init__.py +17 -0
  106. ksadk/identity/resolver.py +385 -0
  107. ksadk/knowledge_base/__init__.py +62 -0
  108. ksadk/knowledge_base/adk_tool.py +47 -0
  109. ksadk/knowledge_base/client.py +297 -0
  110. ksadk/knowledge_base/langchain_tool.py +55 -0
  111. ksadk/knowledge_base/service.py +66 -0
  112. ksadk/knowledge_base/tool.py +50 -0
  113. ksadk/markdown.py +169 -0
  114. ksadk/mcp_runtime/__init__.py +231 -0
  115. ksadk/memory/__init__.py +57 -0
  116. ksadk/memory/adk/__init__.py +51 -0
  117. ksadk/memory/adk/backends/__init__.py +5 -0
  118. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  119. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  120. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  121. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  122. ksadk/memory/adk/long_term_memory.py +273 -0
  123. ksadk/memory/adk/resilient_session_service.py +268 -0
  124. ksadk/memory/adk/short_term_memory.py +304 -0
  125. ksadk/memory/adk_tool.py +31 -0
  126. ksadk/memory/backends/__init__.py +6 -0
  127. ksadk/memory/backends/base.py +103 -0
  128. ksadk/memory/backends/memory.py +112 -0
  129. ksadk/memory/backends/redis.py +146 -0
  130. ksadk/memory/langchain_tool.py +39 -0
  131. ksadk/memory/ltm_backend_factory.py +24 -0
  132. ksadk/memory/manager.py +215 -0
  133. ksadk/memory/service.py +172 -0
  134. ksadk/memory/tool.py +96 -0
  135. ksadk/model_policy.py +223 -0
  136. ksadk/openclaw_gateway.py +378 -0
  137. ksadk/runners/__init__.py +8 -0
  138. ksadk/runners/adk_runner.py +1994 -0
  139. ksadk/runners/base_runner.py +305 -0
  140. ksadk/runners/deepagents_runner.py +13 -0
  141. ksadk/runners/factory.py +73 -0
  142. ksadk/runners/langchain_runner.py +751 -0
  143. ksadk/runners/langgraph_runner.py +985 -0
  144. ksadk/runners/patch_langchain.py +187 -0
  145. ksadk/runners/remote_runner.py +792 -0
  146. ksadk/runners/usage_accumulator.py +38 -0
  147. ksadk/runners/utils/__init__.py +19 -0
  148. ksadk/runners/utils/langfuse.py +230 -0
  149. ksadk/runners/utils/loader.py +62 -0
  150. ksadk/runtime_context.py +166 -0
  151. ksadk/runtime_state.py +25 -0
  152. ksadk/sandbox/__init__.py +28 -0
  153. ksadk/sandbox/backends/__init__.py +9 -0
  154. ksadk/sandbox/backends/e2b.py +154 -0
  155. ksadk/sandbox/backends/local_process.py +131 -0
  156. ksadk/sandbox/base.py +101 -0
  157. ksadk/sandbox/factory.py +53 -0
  158. ksadk/sandbox/registry.py +187 -0
  159. ksadk/server/__init__.py +7 -0
  160. ksadk/server/api_models.py +73 -0
  161. ksadk/server/app.py +3931 -0
  162. ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
  163. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  165. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  166. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  171. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  172. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  177. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  189. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  190. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  195. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  204. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  205. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  207. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  208. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  210. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  213. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  214. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  215. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  218. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  219. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  221. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  222. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
  224. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  225. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
  226. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  227. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
  228. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  229. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  230. ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
  231. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  232. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
  233. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  234. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  235. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  236. ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
  237. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  238. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
  239. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  240. ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
  241. ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
  242. ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
  243. ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
  244. ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
  245. ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
  246. ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
  247. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  248. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  249. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  250. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  251. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  252. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  253. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  254. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  255. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  256. ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
  257. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  258. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  259. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  260. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  261. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  262. ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
  263. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  264. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  265. ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
  266. ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
  267. ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
  268. ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
  269. ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
  270. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  271. ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
  272. ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
  273. ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
  274. ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
  275. ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
  276. ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
  277. ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
  278. ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
  279. ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
  280. ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
  281. ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
  282. ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
  283. ksadk/server/static/assets/dist-D-xOD048.js +1 -0
  284. ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
  285. ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
  286. ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
  287. ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
  288. ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
  289. ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
  290. ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
  291. ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
  292. ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
  293. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  294. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  295. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  296. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  297. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  298. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  299. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  300. ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
  301. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  302. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  303. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  304. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  305. ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
  306. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  307. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  308. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
  309. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  310. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  311. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  312. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
  313. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  314. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  315. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  316. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  317. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  318. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  319. ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
  320. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  321. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  322. ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
  323. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  324. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
  325. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  326. ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
  327. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  328. ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
  329. ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
  330. ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
  331. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  332. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  333. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  334. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  335. ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
  336. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
  337. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  338. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  339. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  340. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  341. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  342. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  343. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  344. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  345. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  346. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  347. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  348. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  349. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  350. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  351. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  352. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
  353. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  354. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  355. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  356. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  357. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  358. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  359. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  360. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  361. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
  362. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  364. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
  365. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  366. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  367. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
  368. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  369. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  370. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
  371. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  372. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  373. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  374. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  375. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  376. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  377. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  378. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  379. ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
  380. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  381. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  382. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  383. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  384. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  385. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  386. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  387. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  388. ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
  389. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  390. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  391. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  392. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  393. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  394. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  395. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  396. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  397. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  398. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  399. ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
  400. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  401. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  402. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
  404. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  405. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  406. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  407. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
  408. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  409. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  410. ksadk/server/static/favicon.svg +1 -0
  411. ksadk/server/static/icons.svg +24 -0
  412. ksadk/server/static/index.html +14 -0
  413. ksadk/server/terminal_sessions.py +551 -0
  414. ksadk/sessions/__init__.py +279 -0
  415. ksadk/sessions/base.py +354 -0
  416. ksadk/sessions/continuity.py +337 -0
  417. ksadk/sessions/errors.py +7 -0
  418. ksadk/sessions/in_memory.py +273 -0
  419. ksadk/sessions/local_service.py +949 -0
  420. ksadk/sessions/postgres_service.py +901 -0
  421. ksadk/sessions/resilience.py +52 -0
  422. ksadk/sessions/resilient.py +360 -0
  423. ksadk/sessions/sqlite_service.py +13 -0
  424. ksadk/skills/__init__.py +9 -0
  425. ksadk/skills/loader.py +46 -0
  426. ksadk/skills/models.py +120 -0
  427. ksadk/skills/package_store.py +104 -0
  428. ksadk/skills/runtime/__init__.py +17 -0
  429. ksadk/skills/runtime/agent.py +183 -0
  430. ksadk/skills/runtime/artifacts.py +39 -0
  431. ksadk/skills/runtime/backends/__init__.py +1 -0
  432. ksadk/skills/runtime/backends/disabled.py +8 -0
  433. ksadk/skills/runtime/backends/e2b.py +168 -0
  434. ksadk/skills/runtime/backends/local.py +95 -0
  435. ksadk/skills/runtime/base.py +103 -0
  436. ksadk/skills/runtime/executor.py +219 -0
  437. ksadk/skills/runtime/factory.py +37 -0
  438. ksadk/skills/runtime/loader.py +131 -0
  439. ksadk/skills/runtime/registry.py +150 -0
  440. ksadk/skills/runtime/request.py +67 -0
  441. ksadk/skills/service_client.py +239 -0
  442. ksadk/skills/service_env.py +58 -0
  443. ksadk/skills/tool_defs.py +221 -0
  444. ksadk/terminal_client.py +69 -0
  445. ksadk/terminal_exec_policy.py +229 -0
  446. ksadk/tools/__init__.py +17 -0
  447. ksadk/tools/gateway.py +230 -0
  448. ksadk/tools/result_budget.py +154 -0
  449. ksadk/toolsets/__init__.py +854 -0
  450. ksadk/toolsets/_langchain.py +11 -0
  451. ksadk/toolsets/platform.py +93 -0
  452. ksadk/toolsets/sandbox.py +310 -0
  453. ksadk/toolsets/skills.py +217 -0
  454. ksadk/toolsets/web.py +266 -0
  455. ksadk/toolsets/workspace.py +917 -0
  456. ksadk/toolsets/workspace_state.py +44 -0
  457. ksadk/tracing/__init__.py +37 -0
  458. ksadk/tracing/exporters/__init__.py +1 -0
  459. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  460. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  461. ksadk/tracing/setup.py +892 -0
  462. ksadk/tracing/span_utils.py +114 -0
  463. ksadk/tui/__init__.py +27 -0
  464. ksadk/tui/loop.py +1877 -0
  465. ksadk/tui/stream_render.py +68 -0
  466. ksadk/ui_config.py +168 -0
  467. ksadk/version.py +4 -0
  468. ksadk_runtime_common/__init__.py +20 -0
  469. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  470. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  471. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  472. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  473. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  474. ksadk_runtime_common/memory_backend/registry.py +56 -0
  475. ksadk_runtime_common/memory_backend/render.py +71 -0
  476. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  477. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  478. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  479. ksadk_runtime_common/workspace_files/constants.py +9 -0
  480. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  481. ksadk_runtime_common/workspace_files/preview.py +96 -0
  482. ksadk_runtime_common/workspace_files/router.py +254 -0
  483. agentengine_sdk_python/__init__.py +0 -3
  484. agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
  485. agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
  486. agentengine_sdk_python-0.6.4.dist-info/top_level.txt +0 -1
@@ -0,0 +1,774 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import io
5
+ import json
6
+ import mimetypes
7
+ import zipfile
8
+ from pathlib import Path, PurePosixPath
9
+ from typing import Any, Dict, List, Mapping, Optional
10
+ from xml.etree import ElementTree as ET
11
+
12
+ from ksadk.conversations.attachment_storage import (
13
+ AttachmentStorageService,
14
+ is_hosted_upload_uri,
15
+ is_runtime_upload_uri,
16
+ parse_file_id,
17
+ )
18
+ from ksadk.sessions.local_service import resolve_local_session_dir
19
+
20
+ _TEXT_MIME_PREFIXES = ("text/",)
21
+ _TEXT_MIME_TYPES = {
22
+ "application/json",
23
+ "application/xml",
24
+ "application/yaml",
25
+ "application/x-yaml",
26
+ "application/x-ndjson",
27
+ }
28
+ _TEXT_FILE_EXTENSIONS = {
29
+ ".txt",
30
+ ".md",
31
+ ".markdown",
32
+ ".json",
33
+ ".yaml",
34
+ ".yml",
35
+ ".csv",
36
+ ".tsv",
37
+ ".log",
38
+ }
39
+ _DOCUMENT_FILE_EXTENSIONS = {
40
+ ".pdf",
41
+ ".docx",
42
+ ".pptx",
43
+ ".xlsx",
44
+ ".html",
45
+ ".htm",
46
+ }
47
+ _IMAGE_FILE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
48
+ _ARCHIVE_FILE_EXTENSIONS = {".zip"}
49
+ _DOCUMENT_MIME_TYPES = {
50
+ "application/pdf",
51
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
52
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
53
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
54
+ "text/html",
55
+ }
56
+ _ARCHIVE_MIME_TYPES = {"application/zip", "application/x-zip-compressed"}
57
+ _IMAGE_MIME_PREFIX = "image/"
58
+ _UPLOAD_URI_SCHEME = "ksadk-upload://"
59
+ _MAX_INLINE_TEXT_CHARS = 20_000
60
+ _MAX_TEXT_EXCERPT_CHARS = 600
61
+ _MAX_PROCESS_BYTES = 20_000_000
62
+ _MAX_ARCHIVE_ENTRY_BYTES = 3_000_000
63
+ _MAX_ARCHIVE_TOTAL_BYTES = 12_000_000
64
+ _MAX_ARCHIVE_ENTRIES = 50
65
+ _NESTED_ARCHIVE_EXTENSIONS = {".zip", ".rar", ".7z", ".tar", ".gz", ".tgz", ".bz2", ".xz"}
66
+ _BLOCKED_EXECUTABLE_EXTENSIONS = {
67
+ ".exe",
68
+ ".dll",
69
+ ".so",
70
+ ".dylib",
71
+ ".bat",
72
+ ".cmd",
73
+ ".ps1",
74
+ ".sh",
75
+ ".bash",
76
+ ".zsh",
77
+ ".app",
78
+ ".msi",
79
+ ".jar",
80
+ ".com",
81
+ ".scr",
82
+ }
83
+
84
+
85
+ def is_textual_mime(mime_type: str) -> bool:
86
+ mime = (mime_type or "").lower()
87
+ if not mime:
88
+ return False
89
+ return mime.startswith(_TEXT_MIME_PREFIXES) or mime in _TEXT_MIME_TYPES
90
+
91
+
92
+ def looks_like_textual_attachment(mime_type: str, display_name: str) -> bool:
93
+ suffix = Path(display_name or "").suffix.lower()
94
+ return is_textual_mime(mime_type) or suffix in _TEXT_FILE_EXTENSIONS
95
+
96
+
97
+ def extract_pdf_text(raw: bytes) -> str:
98
+ try:
99
+ from pypdf import PdfReader
100
+ except Exception:
101
+ return ""
102
+
103
+ try:
104
+ reader = PdfReader(io.BytesIO(raw))
105
+ except Exception:
106
+ return ""
107
+
108
+ segments: List[str] = []
109
+ for page in reader.pages[:10]:
110
+ try:
111
+ page_text = page.extract_text() or ""
112
+ except Exception:
113
+ page_text = ""
114
+ if page_text:
115
+ segments.append(page_text)
116
+
117
+ return "\n".join(segments).strip()
118
+
119
+
120
+ def decode_inline_data(data_b64: str) -> bytes:
121
+ return base64.b64decode((data_b64 or "").strip() + "===")
122
+
123
+
124
+ def resolve_uploads_dir() -> Path:
125
+ uploads_dir = resolve_local_session_dir() / "files"
126
+ uploads_dir.mkdir(parents=True, exist_ok=True)
127
+ return uploads_dir
128
+
129
+
130
+ def _path_within_root(path: Path, root: Path) -> bool:
131
+ resolved = path.expanduser().resolve(strict=False)
132
+ resolved_root = root.expanduser().resolve(strict=False)
133
+ return resolved == resolved_root or resolved_root in resolved.parents
134
+
135
+
136
+ def resolve_attachment_storage_path(file_uri: str) -> Optional[Path]:
137
+ normalized_uri = (file_uri or "").strip()
138
+ if not normalized_uri:
139
+ return None
140
+
141
+ if normalized_uri.startswith("local:"):
142
+ return None
143
+
144
+ if is_runtime_upload_uri(normalized_uri) or is_hosted_upload_uri(normalized_uri):
145
+ file_id = parse_file_id(normalized_uri)
146
+ if not file_id:
147
+ return None
148
+
149
+ restored = AttachmentStorageService().ensure_local_path(normalized_uri)
150
+ if restored is not None and restored.is_file():
151
+ return restored.resolve()
152
+
153
+ uploads_dir = resolve_uploads_dir().resolve()
154
+ safe_file_id = Path(file_id).name
155
+ if not safe_file_id:
156
+ return None
157
+ try:
158
+ candidates = sorted(uploads_dir.iterdir(), key=lambda item: item.name)
159
+ except OSError:
160
+ return None
161
+ for candidate in candidates:
162
+ if not candidate.name.startswith(safe_file_id):
163
+ continue
164
+ resolved = candidate.resolve(strict=False)
165
+ if _path_within_root(resolved, uploads_dir) and resolved.is_file():
166
+ return resolved
167
+
168
+ return None
169
+
170
+
171
+ def read_attachment_bytes(storage_path: Optional[Path], *, size_limit: Optional[int] = None) -> Optional[bytes]:
172
+ if storage_path is None or not storage_path.is_file():
173
+ return None
174
+
175
+ try:
176
+ if size_limit is not None and storage_path.stat().st_size > size_limit:
177
+ return None
178
+ return storage_path.read_bytes()
179
+ except OSError:
180
+ return None
181
+
182
+
183
+ def read_attachment_uri_bytes(
184
+ file_uri: Any,
185
+ *,
186
+ size_limit: Optional[int] = None,
187
+ ) -> Optional[bytes]:
188
+ storage_path = resolve_attachment_storage_path(str(file_uri or ""))
189
+ if storage_path is None:
190
+ return None
191
+ return read_attachment_bytes(storage_path, size_limit=size_limit)
192
+
193
+
194
+ def classify_attachment_kind(mime_type: str, display_name: str) -> str:
195
+ mime = (mime_type or "").lower()
196
+ suffix = Path(display_name or "").suffix.lower()
197
+ if suffix in _IMAGE_FILE_EXTENSIONS or mime.startswith(_IMAGE_MIME_PREFIX):
198
+ return "image"
199
+ if suffix in _ARCHIVE_FILE_EXTENSIONS or mime in _ARCHIVE_MIME_TYPES:
200
+ return "archive"
201
+ if suffix in _DOCUMENT_FILE_EXTENSIONS or mime in _DOCUMENT_MIME_TYPES:
202
+ return "document"
203
+ if suffix in _TEXT_FILE_EXTENSIONS or looks_like_textual_attachment(mime, display_name):
204
+ return "text"
205
+ return "binary"
206
+
207
+
208
+ def perform_ocr(raw: bytes, mime_type: str, display_name: str) -> dict[str, str]:
209
+ rapid_text = _perform_rapidocr(raw)
210
+ if rapid_text:
211
+ return {"text": rapid_text, "engine": "rapidocr_onnxruntime"}
212
+
213
+ tesseract_text = _perform_tesseract_ocr(raw)
214
+ if tesseract_text:
215
+ return {"text": tesseract_text, "engine": "pytesseract"}
216
+
217
+ return {"text": "", "engine": ""}
218
+
219
+
220
+ def build_attachment_results(attachments: List[Dict[str, Any]]) -> list[dict[str, Any]]:
221
+ return [build_attachment_result(attachment) for attachment in attachments or []]
222
+
223
+
224
+ def build_attachment_result(attachment: Mapping[str, Any]) -> dict[str, Any]:
225
+ display_name = str(attachment.get("display_name") or "uploaded_file")
226
+ mime_type = str(attachment.get("mime_type") or "application/octet-stream")
227
+ transport = str(attachment.get("transport") or "")
228
+ file_uri = str(attachment.get("file_uri") or "")
229
+ size_bytes = attachment.get("size_bytes")
230
+ raw = _load_attachment_bytes(attachment)
231
+ if size_bytes is None and raw is not None:
232
+ size_bytes = len(raw)
233
+
234
+ return _build_attachment_result_from_raw(
235
+ display_name=display_name,
236
+ mime_type=mime_type,
237
+ transport=transport,
238
+ file_uri=file_uri,
239
+ size_bytes=size_bytes,
240
+ raw=raw,
241
+ allow_archive=True,
242
+ )
243
+
244
+
245
+ def build_attachment_prompt_text(result: Mapping[str, Any]) -> str:
246
+ display_name = str(result.get("display_name") or "uploaded_file")
247
+ mime_type = str(result.get("mime_type") or "application/octet-stream")
248
+ transport = str(result.get("transport") or "")
249
+ text = str(result.get("text") or "").strip()
250
+ if text:
251
+ return f"[上传文件: {display_name}]\n{text}"
252
+
253
+ archive = result.get("archive")
254
+ if isinstance(archive, Mapping):
255
+ entries = archive.get("entries") or []
256
+ entry_lines = [
257
+ f"- {entry.get('path')}"
258
+ for entry in entries[:10]
259
+ if isinstance(entry, Mapping) and str(entry.get("path") or "").strip()
260
+ ]
261
+ if entry_lines:
262
+ return f"[上传文件: {display_name}]\n压缩包内容:\n" + "\n".join(entry_lines)
263
+
264
+ warnings = [str(item).strip() for item in result.get("warnings") or [] if str(item).strip()]
265
+ if warnings:
266
+ header = (
267
+ f"[上传文件引用: {display_name}, mime={mime_type}]"
268
+ if transport == "reference"
269
+ else f"[上传文件: {display_name}, mime={mime_type}]"
270
+ )
271
+ return header + "\n" + "\n".join(f"- {warning}" for warning in warnings[:5])
272
+
273
+ size_bytes = result.get("size_bytes")
274
+ if size_bytes is not None:
275
+ return (
276
+ "[上传文件: "
277
+ f"{display_name}, "
278
+ f"mime={mime_type}, "
279
+ f"bytes={size_bytes}]"
280
+ )
281
+
282
+ return f"[上传文件引用: {display_name}, mime={mime_type}]"
283
+
284
+
285
+ def compact_attachment_result_for_session(result: Mapping[str, Any]) -> dict[str, Any]:
286
+ compact: dict[str, Any] = {}
287
+ for key in (
288
+ "display_name",
289
+ "mime_type",
290
+ "transport",
291
+ "file_uri",
292
+ "size_bytes",
293
+ "kind",
294
+ "status",
295
+ "warnings",
296
+ "extraction_method",
297
+ "text_excerpt",
298
+ ):
299
+ value = result.get(key)
300
+ if value not in (None, ""):
301
+ compact[key] = value
302
+
303
+ document = result.get("document")
304
+ if isinstance(document, Mapping):
305
+ compact["document"] = {
306
+ key: value
307
+ for key, value in document.items()
308
+ if key in {"format", "page_count", "ocr_engine"} and value not in (None, "")
309
+ }
310
+
311
+ image = result.get("image")
312
+ if isinstance(image, Mapping):
313
+ compact["image"] = {
314
+ key: value
315
+ for key, value in image.items()
316
+ if key in {"ocr_engine"} and value not in (None, "")
317
+ }
318
+
319
+ archive = result.get("archive")
320
+ if isinstance(archive, Mapping):
321
+ compact["archive"] = {
322
+ "entries": list(archive.get("entries") or [])[:10],
323
+ "blocked_entries": list(archive.get("blocked_entries") or [])[:10],
324
+ "extracted_entries": list(archive.get("extracted_entries") or [])[:5],
325
+ }
326
+
327
+ return compact
328
+
329
+
330
+ def _build_attachment_result_from_raw(
331
+ *,
332
+ display_name: str,
333
+ mime_type: str,
334
+ transport: str,
335
+ file_uri: str,
336
+ size_bytes: Any,
337
+ raw: bytes | None,
338
+ allow_archive: bool,
339
+ ) -> dict[str, Any]:
340
+ result: dict[str, Any] = {
341
+ "display_name": display_name,
342
+ "mime_type": mime_type,
343
+ "transport": transport,
344
+ "file_uri": file_uri,
345
+ "size_bytes": size_bytes,
346
+ "kind": classify_attachment_kind(mime_type, display_name),
347
+ "status": "ok",
348
+ "warnings": [],
349
+ "extraction_method": "metadata_only",
350
+ "text_excerpt": "",
351
+ }
352
+
353
+ if raw is None:
354
+ result["status"] = "partial" if size_bytes else "failed"
355
+ result["warnings"] = [
356
+ "附件原始内容不可用,当前仅保留元数据摘要。"
357
+ if size_bytes
358
+ else "附件内容无法读取,请重新上传或检查文件句柄是否仍可访问。"
359
+ ]
360
+ return result
361
+
362
+ if result["kind"] == "text":
363
+ return _extract_text_attachment(result, raw)
364
+ if result["kind"] == "document":
365
+ return _extract_document_attachment(result, raw)
366
+ if result["kind"] == "image":
367
+ return _extract_image_attachment(result, raw)
368
+ if result["kind"] == "archive" and allow_archive:
369
+ return _extract_archive_attachment(result, raw)
370
+
371
+ result["status"] = "partial"
372
+ result["warnings"] = ["当前附件类型暂不支持结构化解析,已保留基础元数据。"]
373
+ return result
374
+
375
+
376
+ def _extract_text_attachment(result: dict[str, Any], raw: bytes) -> dict[str, Any]:
377
+ text = _decode_text_bytes(raw)
378
+ if not text:
379
+ result["status"] = "failed"
380
+ result["warnings"] = ["文本附件解码失败,请确认文件编码或重新导出后重试。"]
381
+ result["extraction_method"] = "text_decode"
382
+ return result
383
+
384
+ return _with_text(result, text=text, extraction_method="text_decode")
385
+
386
+
387
+ def _extract_document_attachment(result: dict[str, Any], raw: bytes) -> dict[str, Any]:
388
+ suffix = Path(str(result.get("display_name") or "")).suffix.lower()
389
+ mime_type = str(result.get("mime_type") or "")
390
+
391
+ if suffix == ".pdf" or mime_type == "application/pdf":
392
+ native_text = extract_pdf_text(raw)
393
+ if native_text and not _pdf_text_quality_is_poor(native_text):
394
+ result["document"] = {"format": "pdf"}
395
+ return _with_text(result, text=native_text, extraction_method="pdf_native")
396
+
397
+ ocr = perform_ocr(raw, mime_type, str(result.get("display_name") or ""))
398
+ if ocr.get("text"):
399
+ result["document"] = {"format": "pdf", "ocr_engine": ocr.get("engine") or ""}
400
+ result["warnings"] = ["PDF 原生文本抽取为空或质量较差,已回退到 OCR。"]
401
+ return _with_text(result, text=ocr["text"], extraction_method="pdf_ocr")
402
+
403
+ result["status"] = "failed"
404
+ result["document"] = {"format": "pdf"}
405
+ result["warnings"] = ["PDF 文本抽取失败,请优先上传可复制文本的 PDF 或更清晰的扫描件。"]
406
+ result["extraction_method"] = "pdf_failed"
407
+ return result
408
+
409
+ extractors = {
410
+ ".docx": (_extract_docx_text, "docx_native", {"format": "docx"}),
411
+ ".pptx": (_extract_pptx_text, "pptx_native", {"format": "pptx"}),
412
+ ".xlsx": (_extract_xlsx_text, "xlsx_native", {"format": "xlsx"}),
413
+ ".html": (_extract_html_text, "html_native", {"format": "html"}),
414
+ ".htm": (_extract_html_text, "html_native", {"format": "html"}),
415
+ }
416
+ extractor = extractors.get(suffix)
417
+ if extractor:
418
+ text = extractor[0](raw)
419
+ result["document"] = dict(extractor[2])
420
+ if text:
421
+ return _with_text(result, text=text, extraction_method=extractor[1])
422
+ result["status"] = "failed"
423
+ result["warnings"] = [f"{suffix.lstrip('.').upper()} 文档抽取失败,请检查文件是否损坏或重新导出后重试。"]
424
+ result["extraction_method"] = extractor[1]
425
+ return result
426
+
427
+ if suffix in _TEXT_FILE_EXTENSIONS:
428
+ result["document"] = {"format": suffix.lstrip(".")}
429
+ return _extract_text_attachment(result, raw)
430
+
431
+ result["status"] = "partial"
432
+ result["document"] = {"format": suffix.lstrip(".") or "document"}
433
+ result["warnings"] = ["当前文档类型暂不支持结构化解析,已保留基础元数据。"]
434
+ return result
435
+
436
+
437
+ def _extract_image_attachment(result: dict[str, Any], raw: bytes) -> dict[str, Any]:
438
+ ocr = perform_ocr(raw, str(result.get("mime_type") or ""), str(result.get("display_name") or ""))
439
+ result["image"] = {"ocr_engine": ocr.get("engine") or ""}
440
+ if ocr.get("text"):
441
+ return _with_text(result, text=ocr["text"], extraction_method="image_ocr")
442
+
443
+ result["status"] = "failed"
444
+ result["warnings"] = ["图片 OCR 未产出可用文本,请上传更清晰的图片或改为 PDF/文本版文档。"]
445
+ result["extraction_method"] = "image_ocr"
446
+ return result
447
+
448
+
449
+ def _extract_archive_attachment(result: dict[str, Any], raw: bytes) -> dict[str, Any]:
450
+ entries: list[dict[str, Any]] = []
451
+ blocked_entries: list[dict[str, Any]] = []
452
+ extracted_entries: list[dict[str, Any]] = []
453
+ warnings: list[str] = []
454
+ extracted_chunks: list[str] = []
455
+ total_safe_bytes = 0
456
+
457
+ try:
458
+ archive = zipfile.ZipFile(io.BytesIO(raw))
459
+ except Exception:
460
+ result["status"] = "failed"
461
+ result["warnings"] = ["ZIP 压缩包无法打开,请确认文件未损坏后重试。"]
462
+ result["extraction_method"] = "zip_enumeration"
463
+ return result
464
+
465
+ with archive:
466
+ infos = archive.infolist()[:_MAX_ARCHIVE_ENTRIES]
467
+ for info in infos:
468
+ normalized_path = _normalize_archive_path(info.filename)
469
+ if not normalized_path:
470
+ warning = f"压缩包内存在危险路径 `{info.filename}`,已阻止提取。"
471
+ warnings.append(warning)
472
+ blocked_entries.append({"path": info.filename, "reason": "path_traversal"})
473
+ continue
474
+
475
+ entry = {"path": normalized_path, "size_bytes": info.file_size}
476
+ entries.append(entry)
477
+
478
+ suffix = Path(normalized_path).suffix.lower()
479
+ if suffix in _NESTED_ARCHIVE_EXTENSIONS:
480
+ warnings.append(f"压缩包内嵌套压缩文件 `{normalized_path}` 不支持处理,已跳过。")
481
+ blocked_entries.append({**entry, "reason": "nested_archive"})
482
+ continue
483
+ if suffix in _BLOCKED_EXECUTABLE_EXTENSIONS:
484
+ warnings.append(f"压缩包内可执行内容 `{normalized_path}` 已被安全策略阻止。")
485
+ blocked_entries.append({**entry, "reason": "executable"})
486
+ continue
487
+ if info.file_size > _MAX_ARCHIVE_ENTRY_BYTES:
488
+ warnings.append(f"压缩包条目 `{normalized_path}` 超出大小限制,已跳过。")
489
+ blocked_entries.append({**entry, "reason": "entry_too_large"})
490
+ continue
491
+ if total_safe_bytes + info.file_size > _MAX_ARCHIVE_TOTAL_BYTES:
492
+ warnings.append("压缩包可安全展开体积超限,剩余文件已停止提取。")
493
+ blocked_entries.append({**entry, "reason": "total_size_exceeded"})
494
+ continue
495
+
496
+ entry_mime = mimetypes.guess_type(normalized_path)[0] or "application/octet-stream"
497
+ entry_kind = classify_attachment_kind(entry_mime, normalized_path)
498
+ if entry_kind not in {"text", "document", "image"}:
499
+ continue
500
+
501
+ try:
502
+ entry_raw = archive.read(info)
503
+ except Exception:
504
+ warnings.append(f"压缩包条目 `{normalized_path}` 读取失败,已跳过。")
505
+ blocked_entries.append({**entry, "reason": "read_failed"})
506
+ continue
507
+
508
+ total_safe_bytes += len(entry_raw)
509
+ child_result = _build_attachment_result_from_raw(
510
+ display_name=normalized_path,
511
+ mime_type=entry_mime,
512
+ transport="archive_entry",
513
+ file_uri="",
514
+ size_bytes=len(entry_raw),
515
+ raw=entry_raw,
516
+ allow_archive=False,
517
+ )
518
+ extracted_entries.append(
519
+ {
520
+ "display_name": normalized_path,
521
+ "kind": child_result.get("kind"),
522
+ "status": child_result.get("status"),
523
+ "text_excerpt": child_result.get("text_excerpt", ""),
524
+ }
525
+ )
526
+ if child_result.get("text"):
527
+ extracted_chunks.append(
528
+ f"[{normalized_path}]\n{child_result['text']}"
529
+ )
530
+
531
+ result["archive"] = {
532
+ "entries": entries,
533
+ "blocked_entries": blocked_entries,
534
+ "extracted_entries": extracted_entries,
535
+ }
536
+ result["warnings"] = warnings
537
+ result["extraction_method"] = "zip_enumeration"
538
+ if extracted_chunks:
539
+ result["status"] = "partial" if warnings else "ok"
540
+ return _with_text(
541
+ result,
542
+ text="\n\n".join(extracted_chunks),
543
+ extraction_method="zip_enumeration",
544
+ status="partial" if warnings else "ok",
545
+ )
546
+
547
+ result["status"] = "partial" if warnings else "ok"
548
+ result["text_excerpt"] = _truncate_text(
549
+ "\n".join(f"- {entry['path']}" for entry in entries[:10]),
550
+ _MAX_TEXT_EXCERPT_CHARS,
551
+ )
552
+ return result
553
+
554
+
555
+ def _with_text(
556
+ result: dict[str, Any],
557
+ *,
558
+ text: str,
559
+ extraction_method: str,
560
+ status: str = "ok",
561
+ ) -> dict[str, Any]:
562
+ normalized = _normalize_text(text)
563
+ if not normalized:
564
+ return result
565
+ result["status"] = status
566
+ result["extraction_method"] = extraction_method
567
+ result["text"] = _truncate_text(normalized, _MAX_INLINE_TEXT_CHARS)
568
+ result["text_excerpt"] = _truncate_text(normalized, _MAX_TEXT_EXCERPT_CHARS)
569
+ return result
570
+
571
+
572
+ def _load_attachment_bytes(attachment: Mapping[str, Any]) -> Optional[bytes]:
573
+ inline_data = attachment.get("data")
574
+ if attachment.get("transport") == "inline" and inline_data:
575
+ try:
576
+ raw = decode_inline_data(str(inline_data))
577
+ except Exception:
578
+ return None
579
+ return raw if len(raw) <= _MAX_PROCESS_BYTES else None
580
+
581
+ raw = read_attachment_uri_bytes(attachment.get("file_uri"), size_limit=_MAX_PROCESS_BYTES)
582
+ if raw is not None:
583
+ return raw
584
+ return None
585
+
586
+
587
+ def _decode_text_bytes(raw: bytes) -> str:
588
+ for encoding in ("utf-8", "utf-8-sig", "gb18030", "latin-1"):
589
+ try:
590
+ text = raw.decode(encoding)
591
+ except Exception:
592
+ continue
593
+ if text:
594
+ return text
595
+ return raw.decode("utf-8", errors="ignore")
596
+
597
+
598
+ def _extract_docx_text(raw: bytes) -> str:
599
+ return _extract_xml_text_from_zip(raw, prefixes=("word/document", "word/header", "word/footer"))
600
+
601
+
602
+ def _extract_pptx_text(raw: bytes) -> str:
603
+ return _extract_xml_text_from_zip(raw, prefixes=("ppt/slides/slide",))
604
+
605
+
606
+ def _extract_xlsx_text(raw: bytes) -> str:
607
+ try:
608
+ archive = zipfile.ZipFile(io.BytesIO(raw))
609
+ except Exception:
610
+ return ""
611
+
612
+ with archive:
613
+ shared_strings: list[str] = []
614
+ if "xl/sharedStrings.xml" in archive.namelist():
615
+ try:
616
+ root = ET.fromstring(archive.read("xl/sharedStrings.xml"))
617
+ for node in root.iter():
618
+ if node.tag.endswith("}t") and node.text:
619
+ shared_strings.append(node.text)
620
+ except Exception:
621
+ shared_strings = []
622
+
623
+ rows: list[str] = []
624
+ for name in sorted(item for item in archive.namelist() if item.startswith("xl/worksheets/") and item.endswith(".xml")):
625
+ try:
626
+ root = ET.fromstring(archive.read(name))
627
+ except Exception:
628
+ continue
629
+ for row in root.iter():
630
+ if not row.tag.endswith("}row"):
631
+ continue
632
+ values: list[str] = []
633
+ for cell in row:
634
+ if not isinstance(cell.tag, str) or not cell.tag.endswith("}c"):
635
+ continue
636
+ cell_type = cell.attrib.get("t")
637
+ value_text = ""
638
+ if cell_type == "s":
639
+ value = next((child.text for child in cell if child.tag.endswith("}v")), "")
640
+ if value and value.isdigit():
641
+ index = int(value)
642
+ if 0 <= index < len(shared_strings):
643
+ value_text = shared_strings[index]
644
+ elif cell_type == "inlineStr":
645
+ value_text = " ".join(
646
+ child.text or ""
647
+ for child in cell.iter()
648
+ if isinstance(child.tag, str) and child.tag.endswith("}t")
649
+ )
650
+ else:
651
+ value_text = next((child.text or "" for child in cell if child.tag.endswith("}v")), "")
652
+ if value_text.strip():
653
+ values.append(value_text.strip())
654
+ if values:
655
+ rows.append("\t".join(values))
656
+ return "\n".join(rows)
657
+
658
+
659
+ def _extract_html_text(raw: bytes) -> str:
660
+ text = _decode_text_bytes(raw)
661
+ try:
662
+ from bs4 import BeautifulSoup
663
+ except Exception:
664
+ return text
665
+
666
+ try:
667
+ soup = BeautifulSoup(text, "html.parser")
668
+ except Exception:
669
+ return text
670
+ return soup.get_text("\n", strip=True)
671
+
672
+
673
+ def _extract_xml_text_from_zip(raw: bytes, *, prefixes: tuple[str, ...]) -> str:
674
+ try:
675
+ archive = zipfile.ZipFile(io.BytesIO(raw))
676
+ except Exception:
677
+ return ""
678
+
679
+ texts: list[str] = []
680
+ with archive:
681
+ for name in sorted(
682
+ item for item in archive.namelist() if item.endswith(".xml") and item.startswith(prefixes)
683
+ ):
684
+ try:
685
+ root = ET.fromstring(archive.read(name))
686
+ except Exception:
687
+ continue
688
+ fragment = [
689
+ node.text.strip()
690
+ for node in root.iter()
691
+ if isinstance(node.tag, str) and node.tag.endswith("}t") and (node.text or "").strip()
692
+ ]
693
+ if fragment:
694
+ texts.append("\n".join(fragment))
695
+ return "\n\n".join(texts)
696
+
697
+
698
+ def _normalize_text(text: str) -> str:
699
+ return str(text or "").strip()
700
+
701
+
702
+ def _truncate_text(text: str, limit: int) -> str:
703
+ normalized = _normalize_text(text)
704
+ if len(normalized) <= limit:
705
+ return normalized
706
+ return normalized[:limit].rstrip() + "\n...[内容已截断]"
707
+
708
+
709
+ def _pdf_text_quality_is_poor(text: str) -> bool:
710
+ normalized = "".join(ch for ch in text if not ch.isspace())
711
+ if len(normalized) < 20:
712
+ return True
713
+ readable = sum(
714
+ 1
715
+ for ch in normalized
716
+ if ch.isalnum() or ("\u4e00" <= ch <= "\u9fff")
717
+ )
718
+ return readable / max(len(normalized), 1) < 0.5
719
+
720
+
721
+ def _perform_rapidocr(raw: bytes) -> str:
722
+ try:
723
+ from rapidocr_onnxruntime import RapidOCR
724
+ except Exception:
725
+ return ""
726
+
727
+ try:
728
+ engine = RapidOCR()
729
+ result, _ = engine(raw)
730
+ except Exception:
731
+ return ""
732
+
733
+ texts = [
734
+ str(item[1]).strip()
735
+ for item in result or []
736
+ if isinstance(item, (list, tuple)) and len(item) > 1 and str(item[1]).strip()
737
+ ]
738
+ return "\n".join(texts).strip()
739
+
740
+
741
+ def _perform_tesseract_ocr(raw: bytes) -> str:
742
+ try:
743
+ from PIL import Image
744
+ import pytesseract
745
+ except Exception:
746
+ return ""
747
+
748
+ try:
749
+ image = Image.open(io.BytesIO(raw))
750
+ except Exception:
751
+ return ""
752
+
753
+ for lang in ("chi_sim+eng", "chi_sim", "eng"):
754
+ try:
755
+ text = pytesseract.image_to_string(image, lang=lang)
756
+ except Exception:
757
+ continue
758
+ if text and text.strip():
759
+ return text.strip()
760
+ return ""
761
+
762
+
763
+ def _normalize_archive_path(path: str) -> str:
764
+ raw_path = (path or "").replace("\\", "/").strip()
765
+ if not raw_path:
766
+ return ""
767
+ pure_path = PurePosixPath(raw_path)
768
+ parts = pure_path.parts
769
+ if any(part in {"..", ""} for part in parts):
770
+ return ""
771
+ normalized = str(pure_path).lstrip("./")
772
+ if not normalized or normalized.startswith("/") or normalized == ".":
773
+ return ""
774
+ return normalized