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.
- agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
- agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
- {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
- ksadk/__init__.py +13 -0
- ksadk/__main__.py +4 -0
- ksadk/a2a/__init__.py +15 -0
- ksadk/a2a/card_builder.py +64 -0
- ksadk/a2a/client.py +309 -0
- ksadk/a2a/executor.py +201 -0
- ksadk/a2a/server.py +79 -0
- ksadk/agents/__init__.py +20 -0
- ksadk/agents/base.py +370 -0
- ksadk/agents/context.py +38 -0
- ksadk/agents/event.py +35 -0
- ksadk/agents/loop_agent.py +71 -0
- ksadk/agents/parallel_agent.py +109 -0
- ksadk/agents/runner_adapter.py +24 -0
- ksadk/agents/sequential_agent.py +34 -0
- ksadk/api/__init__.py +3 -0
- ksadk/api/client.py +2246 -0
- ksadk/builders/__init__.py +20 -0
- ksadk/builders/base.py +66 -0
- ksadk/builders/code_builder.py +1990 -0
- ksadk/builders/container_builder.py +786 -0
- ksadk/builders/framework_requirements.py +59 -0
- ksadk/builders/ks3_uploader.py +389 -0
- ksadk/builders/mcp_builder.py +476 -0
- ksadk/builders/requirements_utils.py +75 -0
- ksadk/cli/__init__.py +531 -0
- ksadk/cli/__main__.py +4 -0
- ksadk/cli/agent_ref.py +204 -0
- ksadk/cli/cmd_a2a.py +167 -0
- ksadk/cli/cmd_agent.py +201 -0
- ksadk/cli/cmd_build.py +270 -0
- ksadk/cli/cmd_completion.py +304 -0
- ksadk/cli/cmd_config.py +758 -0
- ksadk/cli/cmd_create.py +2304 -0
- ksadk/cli/cmd_dashboard.py +900 -0
- ksadk/cli/cmd_deploy.py +704 -0
- ksadk/cli/cmd_destroy.py +345 -0
- ksadk/cli/cmd_files.py +1227 -0
- ksadk/cli/cmd_hermes.py +1263 -0
- ksadk/cli/cmd_invoke.py +1503 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1179 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3922 -0
- ksadk/cli/cmd_run.py +239 -0
- ksadk/cli/cmd_status.py +588 -0
- ksadk/cli/cmd_version.py +522 -0
- ksadk/cli/cmd_web.py +288 -0
- ksadk/cli/deploy_utils.py +87 -0
- ksadk/cli/dry_run.py +214 -0
- ksadk/cli/env_options.py +82 -0
- ksadk/cli/error_utils.py +525 -0
- ksadk/cli/global_options.py +31 -0
- ksadk/cli/local_runtime.py +97 -0
- ksadk/cli/model_catalog.py +136 -0
- ksadk/cli/network_options.py +412 -0
- ksadk/cli/resource_common.py +621 -0
- ksadk/cli/storage.py +84 -0
- ksadk/cli/ui.py +431 -0
- ksadk/cli/workflow_common.py +552 -0
- ksadk/common/__init__.py +14 -0
- ksadk/common/aicp_env.py +128 -0
- ksadk/common/auth.py +190 -0
- ksadk/common/constants.py +84 -0
- ksadk/common/llm_utils.py +61 -0
- ksadk/configs/__init__.py +61 -0
- ksadk/configs/env_registry.py +343 -0
- ksadk/configs/global_config.py +200 -0
- ksadk/configs/settings.py +706 -0
- ksadk/conversations/__init__.py +95 -0
- ksadk/conversations/attachment_storage.py +379 -0
- ksadk/conversations/attachments.py +774 -0
- ksadk/conversations/compaction_pipeline.py +387 -0
- ksadk/conversations/compaction_prompt.py +128 -0
- ksadk/conversations/context.py +303 -0
- ksadk/conversations/message_projection.py +269 -0
- ksadk/conversations/model_context.py +370 -0
- ksadk/conversations/model_options.py +136 -0
- ksadk/conversations/normalize.py +446 -0
- ksadk/conversations/reasoning_markup.py +91 -0
- ksadk/conversations/run_kinds.py +61 -0
- ksadk/conversations/run_status.py +53 -0
- ksadk/conversations/runtime.py +5276 -0
- ksadk/conversations/semantic_summary.py +359 -0
- ksadk/conversations/session_title.py +254 -0
- ksadk/deployment/__init__.py +83 -0
- ksadk/deployment/agent_access.py +226 -0
- ksadk/deployment/base.py +229 -0
- ksadk/deployment/manager.py +332 -0
- ksadk/deployment/providers/__init__.py +10 -0
- ksadk/deployment/providers/serverless.py +1115 -0
- ksadk/deployment/registry.py +120 -0
- ksadk/deployment/state.py +78 -0
- ksadk/deployment/ui_config.py +21 -0
- ksadk/detection/__init__.py +7 -0
- ksadk/detection/detector.py +431 -0
- ksadk/detection/mcp_detector.py +250 -0
- ksadk/hermes_terminal.py +467 -0
- ksadk/identity/__init__.py +17 -0
- ksadk/identity/resolver.py +385 -0
- ksadk/knowledge_base/__init__.py +62 -0
- ksadk/knowledge_base/adk_tool.py +47 -0
- ksadk/knowledge_base/client.py +297 -0
- ksadk/knowledge_base/langchain_tool.py +55 -0
- ksadk/knowledge_base/service.py +66 -0
- ksadk/knowledge_base/tool.py +50 -0
- ksadk/markdown.py +169 -0
- ksadk/mcp_runtime/__init__.py +231 -0
- ksadk/memory/__init__.py +57 -0
- ksadk/memory/adk/__init__.py +51 -0
- ksadk/memory/adk/backends/__init__.py +5 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
- ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
- ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
- ksadk/memory/adk/long_term_memory.py +273 -0
- ksadk/memory/adk/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +304 -0
- ksadk/memory/adk_tool.py +31 -0
- ksadk/memory/backends/__init__.py +6 -0
- ksadk/memory/backends/base.py +103 -0
- ksadk/memory/backends/memory.py +112 -0
- ksadk/memory/backends/redis.py +146 -0
- ksadk/memory/langchain_tool.py +39 -0
- ksadk/memory/ltm_backend_factory.py +24 -0
- ksadk/memory/manager.py +215 -0
- ksadk/memory/service.py +172 -0
- ksadk/memory/tool.py +96 -0
- ksadk/model_policy.py +223 -0
- ksadk/openclaw_gateway.py +378 -0
- ksadk/runners/__init__.py +8 -0
- ksadk/runners/adk_runner.py +1994 -0
- ksadk/runners/base_runner.py +305 -0
- ksadk/runners/deepagents_runner.py +13 -0
- ksadk/runners/factory.py +73 -0
- ksadk/runners/langchain_runner.py +751 -0
- ksadk/runners/langgraph_runner.py +985 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +792 -0
- ksadk/runners/usage_accumulator.py +38 -0
- ksadk/runners/utils/__init__.py +19 -0
- ksadk/runners/utils/langfuse.py +230 -0
- ksadk/runners/utils/loader.py +62 -0
- ksadk/runtime_context.py +166 -0
- ksadk/runtime_state.py +25 -0
- ksadk/sandbox/__init__.py +28 -0
- ksadk/sandbox/backends/__init__.py +9 -0
- ksadk/sandbox/backends/e2b.py +154 -0
- ksadk/sandbox/backends/local_process.py +131 -0
- ksadk/sandbox/base.py +101 -0
- ksadk/sandbox/factory.py +53 -0
- ksadk/sandbox/registry.py +187 -0
- ksadk/server/__init__.py +7 -0
- ksadk/server/api_models.py +73 -0
- ksadk/server/app.py +3931 -0
- ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
- ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
- ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
- ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
- ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
- ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
- ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
- ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
- ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
- ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
- ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
- ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
- ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
- ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
- ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
- ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
- ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
- ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
- ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
- ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
- ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
- ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
- ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
- ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
- ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
- ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
- ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
- ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
- ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
- ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
- ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
- ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
- ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
- ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
- ksadk/server/static/assets/dist-D-xOD048.js +1 -0
- ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
- ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
- ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
- ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
- ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
- ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
- ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
- ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
- ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
- ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
- ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
- ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
- ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
- ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
- ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
- ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
- ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
- ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
- ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
- ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
- ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
- ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
- ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
- ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
- ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
- ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
- ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
- ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
- ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
- ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
- ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
- ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
- ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
- ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
- ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
- ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
- ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
- ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
- ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
- ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
- ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
- ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
- ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
- ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
- ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
- ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
- ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
- ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
- ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
- ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
- ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
- ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
- ksadk/server/static/assets/properties-C357ku5U.js +1 -0
- ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
- ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
- ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
- ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
- ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
- ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
- ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
- ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
- ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
- ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
- ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
- ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
- ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
- ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
- ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
- ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
- ksadk/server/static/assets/solr-BBopId9w.js +1 -0
- ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
- ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
- ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
- ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
- ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
- ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
- ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
- ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
- ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
- ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
- ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
- ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
- ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
- ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
- ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
- ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
- ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
- ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
- ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
- ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
- ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
- ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
- ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
- ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
- ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
- ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
- ksadk/server/static/favicon.svg +1 -0
- ksadk/server/static/icons.svg +24 -0
- ksadk/server/static/index.html +14 -0
- ksadk/server/terminal_sessions.py +551 -0
- ksadk/sessions/__init__.py +279 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +337 -0
- ksadk/sessions/errors.py +7 -0
- ksadk/sessions/in_memory.py +273 -0
- ksadk/sessions/local_service.py +949 -0
- ksadk/sessions/postgres_service.py +901 -0
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -0
- ksadk/sessions/sqlite_service.py +13 -0
- ksadk/skills/__init__.py +9 -0
- ksadk/skills/loader.py +46 -0
- ksadk/skills/models.py +120 -0
- ksadk/skills/package_store.py +104 -0
- ksadk/skills/runtime/__init__.py +17 -0
- ksadk/skills/runtime/agent.py +183 -0
- ksadk/skills/runtime/artifacts.py +39 -0
- ksadk/skills/runtime/backends/__init__.py +1 -0
- ksadk/skills/runtime/backends/disabled.py +8 -0
- ksadk/skills/runtime/backends/e2b.py +168 -0
- ksadk/skills/runtime/backends/local.py +95 -0
- ksadk/skills/runtime/base.py +103 -0
- ksadk/skills/runtime/executor.py +219 -0
- ksadk/skills/runtime/factory.py +37 -0
- ksadk/skills/runtime/loader.py +131 -0
- ksadk/skills/runtime/registry.py +150 -0
- ksadk/skills/runtime/request.py +67 -0
- ksadk/skills/service_client.py +239 -0
- ksadk/skills/service_env.py +58 -0
- ksadk/skills/tool_defs.py +221 -0
- ksadk/terminal_client.py +69 -0
- ksadk/terminal_exec_policy.py +229 -0
- ksadk/tools/__init__.py +17 -0
- ksadk/tools/gateway.py +230 -0
- ksadk/tools/result_budget.py +154 -0
- ksadk/toolsets/__init__.py +854 -0
- ksadk/toolsets/_langchain.py +11 -0
- ksadk/toolsets/platform.py +93 -0
- ksadk/toolsets/sandbox.py +310 -0
- ksadk/toolsets/skills.py +217 -0
- ksadk/toolsets/web.py +266 -0
- ksadk/toolsets/workspace.py +917 -0
- ksadk/toolsets/workspace_state.py +44 -0
- ksadk/tracing/__init__.py +37 -0
- ksadk/tracing/exporters/__init__.py +1 -0
- ksadk/tracing/exporters/inmemory_exporter.py +116 -0
- ksadk/tracing/exporters/langfuse_exporter.py +445 -0
- ksadk/tracing/setup.py +892 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +27 -0
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -0
- ksadk/ui_config.py +168 -0
- ksadk/version.py +4 -0
- ksadk_runtime_common/__init__.py +20 -0
- ksadk_runtime_common/memory_backend/__init__.py +29 -0
- ksadk_runtime_common/memory_backend/manifest.py +79 -0
- ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
- ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
- ksadk_runtime_common/memory_backend/registry.py +56 -0
- ksadk_runtime_common/memory_backend/render.py +71 -0
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
- ksadk_runtime_common/workspace_files/__init__.py +29 -0
- ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
- ksadk_runtime_common/workspace_files/constants.py +9 -0
- ksadk_runtime_common/workspace_files/path_utils.py +71 -0
- ksadk_runtime_common/workspace_files/preview.py +96 -0
- ksadk_runtime_common/workspace_files/router.py +254 -0
- agentengine_sdk_python/__init__.py +0 -3
- agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
- agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
- 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
|