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,917 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import difflib
|
|
5
|
+
import fnmatch
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ksadk.sessions.local_service import resolve_local_session_dir
|
|
14
|
+
from ksadk.tools.gateway import ToolPolicy, default_tool_gateway
|
|
15
|
+
from ksadk.tools.result_budget import budget_tool_output, default_tool_result_budget
|
|
16
|
+
from ksadk.toolsets._langchain import as_tool
|
|
17
|
+
from ksadk.toolsets.workspace_state import WorkspaceReadState, get_read_state, record_read_state
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_WORKSPACE_TOOL_POLICIES = {
|
|
21
|
+
"workspace_status": ToolPolicy(risk_level="low"),
|
|
22
|
+
"list_workspace_files": ToolPolicy(risk_level="low"),
|
|
23
|
+
"read_workspace_file": ToolPolicy(risk_level="low"),
|
|
24
|
+
"write_workspace_file": ToolPolicy(risk_level="medium", side_effects=("workspace_write",)),
|
|
25
|
+
"write_workspace_files": ToolPolicy(risk_level="medium", side_effects=("workspace_write",)),
|
|
26
|
+
"edit_workspace_file": ToolPolicy(risk_level="medium", side_effects=("workspace_edit",)),
|
|
27
|
+
"multi_edit_workspace_file": ToolPolicy(risk_level="medium", side_effects=("workspace_edit",)),
|
|
28
|
+
"lint_workspace_file": ToolPolicy(risk_level="low"),
|
|
29
|
+
"search_workspace_files": ToolPolicy(risk_level="low"),
|
|
30
|
+
"delete_workspace_file": ToolPolicy(risk_level="high", side_effects=("workspace_delete",)),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _gateway():
|
|
35
|
+
return default_tool_gateway(_WORKSPACE_TOOL_POLICIES)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def workspace_root() -> Path:
|
|
39
|
+
return resolve_local_session_dir() / "workspace"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def resolve_workspace_path(relative_path: str) -> Path:
|
|
43
|
+
root = workspace_root().resolve()
|
|
44
|
+
raw = str(relative_path or "").strip().replace("\\", "/").lstrip("/") or "."
|
|
45
|
+
target = (root / raw).resolve()
|
|
46
|
+
if target != root and root not in target.parents:
|
|
47
|
+
raise ValueError("workspace path must stay inside the workspace root")
|
|
48
|
+
return target
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def workspace_relative(path: Path) -> str:
|
|
52
|
+
return path.resolve().relative_to(workspace_root().resolve()).as_posix()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def workspace_status() -> dict[str, Any]:
|
|
56
|
+
"""Return current AgentEngine workspace status."""
|
|
57
|
+
|
|
58
|
+
return _gateway().invoke("workspace_status", _workspace_status_impl)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _workspace_status_impl() -> dict[str, Any]:
|
|
62
|
+
root = workspace_root()
|
|
63
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
files = [
|
|
65
|
+
{"path": workspace_relative(path), "size": path.stat().st_size}
|
|
66
|
+
for path in sorted(item for item in root.rglob("*") if item.is_file())[:50]
|
|
67
|
+
]
|
|
68
|
+
return {
|
|
69
|
+
"ok": True,
|
|
70
|
+
"workspace_root": str(root),
|
|
71
|
+
"file_count_sampled": len(files),
|
|
72
|
+
"files": files,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def list_workspace_files(
|
|
77
|
+
path: str = ".",
|
|
78
|
+
glob: str | None = None,
|
|
79
|
+
recursive: bool = False,
|
|
80
|
+
include_dirs: bool = True,
|
|
81
|
+
max_results: int = 500,
|
|
82
|
+
sort_by: str = "name",
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
"""List files under the AgentEngine workspace."""
|
|
85
|
+
|
|
86
|
+
return _gateway().invoke(
|
|
87
|
+
"list_workspace_files",
|
|
88
|
+
_list_workspace_files_impl,
|
|
89
|
+
path,
|
|
90
|
+
glob,
|
|
91
|
+
recursive,
|
|
92
|
+
include_dirs,
|
|
93
|
+
max_results,
|
|
94
|
+
sort_by,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _list_workspace_files_impl(
|
|
99
|
+
path: str = ".",
|
|
100
|
+
glob: str | None = None,
|
|
101
|
+
recursive: bool = False,
|
|
102
|
+
include_dirs: bool = True,
|
|
103
|
+
max_results: int = 500,
|
|
104
|
+
sort_by: str = "name",
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
target = resolve_workspace_path(path)
|
|
107
|
+
if not target.exists():
|
|
108
|
+
return {"ok": False, "error_message": f"workspace path not found: {path}"}
|
|
109
|
+
if target.is_file():
|
|
110
|
+
items = [target]
|
|
111
|
+
elif recursive:
|
|
112
|
+
items = [item for item in target.rglob("*") if item != target]
|
|
113
|
+
else:
|
|
114
|
+
items = list(target.iterdir())
|
|
115
|
+
pattern = str(glob or "").strip()
|
|
116
|
+
if pattern:
|
|
117
|
+
items = [
|
|
118
|
+
item
|
|
119
|
+
for item in items
|
|
120
|
+
if fnmatch.fnmatch(workspace_relative(item), pattern) or fnmatch.fnmatch(item.name, pattern)
|
|
121
|
+
]
|
|
122
|
+
if not include_dirs:
|
|
123
|
+
items = [item for item in items if item.is_file()]
|
|
124
|
+
sort_key = str(sort_by or "name").strip().lower()
|
|
125
|
+
if sort_key == "mtime":
|
|
126
|
+
key = lambda candidate: (candidate.stat().st_mtime_ns, workspace_relative(candidate))
|
|
127
|
+
elif sort_key == "size":
|
|
128
|
+
key = lambda candidate: (candidate.stat().st_size if candidate.is_file() else 0, workspace_relative(candidate))
|
|
129
|
+
else:
|
|
130
|
+
key = lambda candidate: (workspace_relative(candidate).lower(), candidate.is_file())
|
|
131
|
+
limit = max(1, min(int(max_results or 500), 5000))
|
|
132
|
+
sorted_items = sorted(items, key=key)
|
|
133
|
+
entries = [
|
|
134
|
+
{
|
|
135
|
+
"name": item.name,
|
|
136
|
+
"path": workspace_relative(item),
|
|
137
|
+
"type": "directory" if item.is_dir() else "file",
|
|
138
|
+
"size": item.stat().st_size if item.is_file() else 0,
|
|
139
|
+
"mtime_ns": item.stat().st_mtime_ns,
|
|
140
|
+
}
|
|
141
|
+
for item in sorted_items[:limit]
|
|
142
|
+
]
|
|
143
|
+
return {"ok": True, "path": path, "recursive": recursive, "entries": entries, "truncated": len(sorted_items) > len(entries)}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_workspace_file(
|
|
147
|
+
path: str,
|
|
148
|
+
start_line: int | None = None,
|
|
149
|
+
end_line: int | None = None,
|
|
150
|
+
max_chars: int | None = None,
|
|
151
|
+
include_line_numbers: bool = True,
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
"""Read a UTF-8 workspace file. Inspect the result before editing it.
|
|
154
|
+
|
|
155
|
+
Do not issue a dependent read and edit in the same parallel tool-call batch.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
return _gateway().invoke(
|
|
159
|
+
"read_workspace_file",
|
|
160
|
+
_read_workspace_file_impl,
|
|
161
|
+
path,
|
|
162
|
+
start_line,
|
|
163
|
+
end_line,
|
|
164
|
+
max_chars,
|
|
165
|
+
include_line_numbers,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _read_workspace_file_impl(
|
|
170
|
+
path: str,
|
|
171
|
+
start_line: int | None = None,
|
|
172
|
+
end_line: int | None = None,
|
|
173
|
+
max_chars: int | None = None,
|
|
174
|
+
include_line_numbers: bool = True,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
target = resolve_workspace_path(path)
|
|
177
|
+
if not target.is_file():
|
|
178
|
+
return {"ok": False, "error_message": f"workspace file not found: {path}"}
|
|
179
|
+
stat = target.stat()
|
|
180
|
+
if stat.st_size > 2 * 1024 * 1024:
|
|
181
|
+
return {"ok": False, "error_message": "file is larger than 2MB"}
|
|
182
|
+
try:
|
|
183
|
+
text = target.read_text(encoding="utf-8")
|
|
184
|
+
except UnicodeDecodeError:
|
|
185
|
+
return {"ok": False, "error_message": "file is not utf-8 text"}
|
|
186
|
+
lines = text.splitlines()
|
|
187
|
+
total_lines = len(lines)
|
|
188
|
+
start = max(1, int(start_line or 1))
|
|
189
|
+
end = min(total_lines, int(end_line or total_lines)) if total_lines else 0
|
|
190
|
+
if end and start > end:
|
|
191
|
+
return {"ok": False, "error_message": "start_line must be <= end_line"}
|
|
192
|
+
selected = lines[start - 1 : end] if total_lines else []
|
|
193
|
+
content = "\n".join(
|
|
194
|
+
f"{line_no} | {line}" if include_line_numbers else line
|
|
195
|
+
for line_no, line in enumerate(selected, start=start)
|
|
196
|
+
)
|
|
197
|
+
limit = max(1000, min(int(max_chars or 20000), 100000))
|
|
198
|
+
relative = workspace_relative(target)
|
|
199
|
+
budgeted = budget_tool_output(
|
|
200
|
+
tool_name="read_workspace_file",
|
|
201
|
+
field_name="content",
|
|
202
|
+
value=content,
|
|
203
|
+
metadata={"tool_use_id": f"read_{relative.replace('/', '_')}"},
|
|
204
|
+
budget=default_tool_result_budget(max_chars=limit, preview_chars=limit, persist_threshold_chars=limit),
|
|
205
|
+
)
|
|
206
|
+
partial = start != 1 or end != total_lines
|
|
207
|
+
record_read_state(
|
|
208
|
+
WorkspaceReadState(
|
|
209
|
+
path=relative,
|
|
210
|
+
mtime_ns=stat.st_mtime_ns,
|
|
211
|
+
size_bytes=stat.st_size,
|
|
212
|
+
start_line=start,
|
|
213
|
+
end_line=end,
|
|
214
|
+
total_lines=total_lines,
|
|
215
|
+
partial=partial or bool(budgeted.get("truncated")),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
return {
|
|
219
|
+
"ok": True,
|
|
220
|
+
"path": relative,
|
|
221
|
+
"size": stat.st_size,
|
|
222
|
+
"size_bytes": stat.st_size,
|
|
223
|
+
"mtime_ns": stat.st_mtime_ns,
|
|
224
|
+
"start_line": start,
|
|
225
|
+
"end_line": end,
|
|
226
|
+
"line_count": len(selected),
|
|
227
|
+
"read_range": {"start": start, "end": end},
|
|
228
|
+
"total_lines": total_lines,
|
|
229
|
+
"suggested_action": "",
|
|
230
|
+
"partial": partial or bool(budgeted.get("truncated")),
|
|
231
|
+
**budgeted,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _read_workspace_text(target: Path) -> tuple[str | None, dict[str, Any] | None]:
|
|
236
|
+
if not target.is_file():
|
|
237
|
+
return None, {"ok": False, "error_message": "workspace file not found"}
|
|
238
|
+
if target.stat().st_size > 2 * 1024 * 1024:
|
|
239
|
+
return None, {"ok": False, "error_message": "file is larger than 2MB"}
|
|
240
|
+
try:
|
|
241
|
+
return target.read_text(encoding="utf-8"), None
|
|
242
|
+
except UnicodeDecodeError:
|
|
243
|
+
return None, {"ok": False, "error_message": "file is not utf-8 text"}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def write_workspace_file(
|
|
247
|
+
path: str,
|
|
248
|
+
content: str,
|
|
249
|
+
overwrite: bool = True,
|
|
250
|
+
approval: dict[str, Any] | None = None,
|
|
251
|
+
) -> dict[str, Any]:
|
|
252
|
+
"""Write a UTF-8 text file inside the AgentEngine workspace."""
|
|
253
|
+
|
|
254
|
+
return _gateway().invoke(
|
|
255
|
+
"write_workspace_file",
|
|
256
|
+
_write_workspace_file_impl,
|
|
257
|
+
path,
|
|
258
|
+
content,
|
|
259
|
+
overwrite,
|
|
260
|
+
approval=approval,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _write_workspace_file_impl(path: str, content: str, overwrite: bool = True) -> dict[str, Any]:
|
|
265
|
+
target = resolve_workspace_path(path)
|
|
266
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
if target.exists() and not overwrite:
|
|
268
|
+
return {"ok": False, "error_message": f"workspace file already exists: {path}"}
|
|
269
|
+
target.write_text(content or "", encoding="utf-8")
|
|
270
|
+
return {
|
|
271
|
+
"ok": True,
|
|
272
|
+
"path": workspace_relative(target),
|
|
273
|
+
"absolute_path": str(target),
|
|
274
|
+
"size": target.stat().st_size,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def write_workspace_files(
|
|
279
|
+
files: list[dict[str, Any]],
|
|
280
|
+
overwrite: bool = True,
|
|
281
|
+
approval: dict[str, Any] | None = None,
|
|
282
|
+
) -> dict[str, Any]:
|
|
283
|
+
"""Write multiple UTF-8 text files inside the AgentEngine workspace."""
|
|
284
|
+
|
|
285
|
+
return _gateway().invoke(
|
|
286
|
+
"write_workspace_files",
|
|
287
|
+
_write_workspace_files_impl,
|
|
288
|
+
files,
|
|
289
|
+
overwrite,
|
|
290
|
+
approval=approval,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _write_workspace_files_impl(files: list[dict[str, Any]], overwrite: bool = True) -> dict[str, Any]:
|
|
295
|
+
if not isinstance(files, list) or not files:
|
|
296
|
+
return {"ok": False, "error_message": "files must be a non-empty list"}
|
|
297
|
+
written = []
|
|
298
|
+
for item in files[:100]:
|
|
299
|
+
if not isinstance(item, dict):
|
|
300
|
+
return {"ok": False, "error_message": "each file item must be an object"}
|
|
301
|
+
path = str(item.get("path") or "").strip()
|
|
302
|
+
if not path:
|
|
303
|
+
return {"ok": False, "error_message": "each file item requires path"}
|
|
304
|
+
result = _write_workspace_file_impl(path, str(item.get("content") or ""), overwrite=overwrite)
|
|
305
|
+
if not result.get("ok"):
|
|
306
|
+
return result
|
|
307
|
+
written.append({"path": result["path"], "size": result["size"]})
|
|
308
|
+
return {"ok": True, "written": written, "truncated": len(files) > len(written)}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def edit_workspace_file(
|
|
312
|
+
path: str,
|
|
313
|
+
old_text: str,
|
|
314
|
+
new_text: str,
|
|
315
|
+
expected_replacements: int = 1,
|
|
316
|
+
replace_all: bool = False,
|
|
317
|
+
approval: dict[str, Any] | None = None,
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
"""Replace exact text after reading the file in a prior model step.
|
|
320
|
+
|
|
321
|
+
The read must complete before this call; do not batch a dependent read and edit.
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
return _gateway().invoke(
|
|
325
|
+
"edit_workspace_file",
|
|
326
|
+
_edit_workspace_file_impl,
|
|
327
|
+
path,
|
|
328
|
+
old_text,
|
|
329
|
+
new_text,
|
|
330
|
+
expected_replacements,
|
|
331
|
+
replace_all,
|
|
332
|
+
approval=approval,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _edit_workspace_file_impl(
|
|
337
|
+
path: str,
|
|
338
|
+
old_text: str,
|
|
339
|
+
new_text: str,
|
|
340
|
+
expected_replacements: int = 1,
|
|
341
|
+
replace_all: bool = False,
|
|
342
|
+
) -> dict[str, Any]:
|
|
343
|
+
target = resolve_workspace_path(path)
|
|
344
|
+
relative = workspace_relative(target)
|
|
345
|
+
request_error = _validate_single_edit_request(old_text, relative=relative)
|
|
346
|
+
if request_error is not None:
|
|
347
|
+
return request_error
|
|
348
|
+
read_error = _validate_read_state(target, relative)
|
|
349
|
+
if read_error is not None:
|
|
350
|
+
return read_error
|
|
351
|
+
text, error = _read_workspace_text(target)
|
|
352
|
+
if error is not None:
|
|
353
|
+
return {**error, "path": path}
|
|
354
|
+
edit_result = _apply_single_edit(
|
|
355
|
+
text,
|
|
356
|
+
old_text,
|
|
357
|
+
new_text,
|
|
358
|
+
expected_replacements=expected_replacements,
|
|
359
|
+
replace_all=replace_all,
|
|
360
|
+
relative=relative,
|
|
361
|
+
)
|
|
362
|
+
if not edit_result["ok"]:
|
|
363
|
+
return edit_result
|
|
364
|
+
updated = str(edit_result["updated"])
|
|
365
|
+
diff = _build_diff(relative, text, updated)
|
|
366
|
+
target.write_text(updated, encoding="utf-8")
|
|
367
|
+
new_stat = target.stat()
|
|
368
|
+
_refresh_read_state(relative, new_stat, updated)
|
|
369
|
+
diff_budget = _budget_diff("edit_workspace_file", diff, relative)
|
|
370
|
+
return {
|
|
371
|
+
"ok": True,
|
|
372
|
+
"path": relative,
|
|
373
|
+
"absolute_path": str(target),
|
|
374
|
+
"replacements": edit_result["replacements"],
|
|
375
|
+
"used_quote_normalization": edit_result["used_quote_normalization"],
|
|
376
|
+
**diff_budget,
|
|
377
|
+
"size": new_stat.st_size,
|
|
378
|
+
"mtime_ns": new_stat.st_mtime_ns,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def multi_edit_workspace_file(
|
|
383
|
+
path: str,
|
|
384
|
+
edits: list[dict[str, Any]],
|
|
385
|
+
replace_all: bool = False,
|
|
386
|
+
approval: dict[str, Any] | None = None,
|
|
387
|
+
) -> dict[str, Any]:
|
|
388
|
+
"""Apply atomic exact edits after reading the file in a prior model step.
|
|
389
|
+
|
|
390
|
+
The read must complete before this call; do not batch a dependent read and edit.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
return _gateway().invoke(
|
|
394
|
+
"multi_edit_workspace_file",
|
|
395
|
+
_multi_edit_workspace_file_impl,
|
|
396
|
+
path,
|
|
397
|
+
edits,
|
|
398
|
+
replace_all,
|
|
399
|
+
approval=approval,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _multi_edit_workspace_file_impl(
|
|
404
|
+
path: str,
|
|
405
|
+
edits: list[dict[str, Any]],
|
|
406
|
+
replace_all: bool = False,
|
|
407
|
+
) -> dict[str, Any]:
|
|
408
|
+
target = resolve_workspace_path(path)
|
|
409
|
+
relative = workspace_relative(target)
|
|
410
|
+
if not isinstance(edits, list) or not edits:
|
|
411
|
+
return {
|
|
412
|
+
"ok": False,
|
|
413
|
+
"path": relative,
|
|
414
|
+
"error_type": "invalid_edits",
|
|
415
|
+
"error_message": "edits must be a non-empty list",
|
|
416
|
+
}
|
|
417
|
+
for index, edit in enumerate(edits):
|
|
418
|
+
if not isinstance(edit, dict):
|
|
419
|
+
return {
|
|
420
|
+
"ok": False,
|
|
421
|
+
"path": relative,
|
|
422
|
+
"error_type": "invalid_edit",
|
|
423
|
+
"error_message": f"edit at index {index} must be an object",
|
|
424
|
+
"failed_edit_index": index,
|
|
425
|
+
}
|
|
426
|
+
request_error = _validate_single_edit_request(edit.get("old_text"), relative=relative)
|
|
427
|
+
if request_error is not None:
|
|
428
|
+
return {**request_error, "failed_edit_index": index, "edit_count": len(edits)}
|
|
429
|
+
read_error = _validate_read_state(target, relative)
|
|
430
|
+
if read_error is not None:
|
|
431
|
+
return read_error
|
|
432
|
+
text, error = _read_workspace_text(target)
|
|
433
|
+
if error is not None:
|
|
434
|
+
return {**error, "path": path}
|
|
435
|
+
original = text or ""
|
|
436
|
+
updated = original
|
|
437
|
+
diagnostics: list[dict[str, Any]] = []
|
|
438
|
+
total_replacements = 0
|
|
439
|
+
for index, edit in enumerate(edits):
|
|
440
|
+
edit_result = _apply_single_edit(
|
|
441
|
+
updated,
|
|
442
|
+
edit.get("old_text"),
|
|
443
|
+
edit.get("new_text"),
|
|
444
|
+
expected_replacements=int(edit.get("expected_replacements") or 1),
|
|
445
|
+
replace_all=bool(edit.get("replace_all", replace_all)),
|
|
446
|
+
relative=relative,
|
|
447
|
+
)
|
|
448
|
+
if not edit_result["ok"]:
|
|
449
|
+
return {**edit_result, "failed_edit_index": index, "edit_count": len(edits)}
|
|
450
|
+
updated = str(edit_result["updated"])
|
|
451
|
+
total_replacements += int(edit_result["replacements"])
|
|
452
|
+
diagnostics.append(
|
|
453
|
+
{
|
|
454
|
+
"index": index,
|
|
455
|
+
"replacements": edit_result["replacements"],
|
|
456
|
+
"used_quote_normalization": edit_result["used_quote_normalization"],
|
|
457
|
+
"matches": edit_result.get("matches", []),
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
diff = _build_diff(relative, original, updated)
|
|
461
|
+
target.write_text(updated, encoding="utf-8")
|
|
462
|
+
new_stat = target.stat()
|
|
463
|
+
_refresh_read_state(relative, new_stat, updated)
|
|
464
|
+
diff_budget = _budget_diff("multi_edit_workspace_file", diff, relative)
|
|
465
|
+
return {
|
|
466
|
+
"ok": True,
|
|
467
|
+
"path": relative,
|
|
468
|
+
"absolute_path": str(target),
|
|
469
|
+
"edit_count": len(edits),
|
|
470
|
+
"replacements": total_replacements,
|
|
471
|
+
"edits": diagnostics,
|
|
472
|
+
**diff_budget,
|
|
473
|
+
"size": new_stat.st_size,
|
|
474
|
+
"mtime_ns": new_stat.st_mtime_ns,
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def lint_workspace_file(path: str, language: str = "auto") -> dict[str, Any]:
|
|
479
|
+
"""Run lightweight built-in lint checks for a UTF-8 workspace text file."""
|
|
480
|
+
|
|
481
|
+
return _gateway().invoke("lint_workspace_file", _lint_workspace_file_impl, path, language)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _lint_workspace_file_impl(path: str, language: str = "auto") -> dict[str, Any]:
|
|
485
|
+
target = resolve_workspace_path(path)
|
|
486
|
+
text, error = _read_workspace_text(target)
|
|
487
|
+
if error is not None:
|
|
488
|
+
return {**error, "path": path}
|
|
489
|
+
language_name = _detect_language(target, language)
|
|
490
|
+
issues: list[dict[str, Any]] = []
|
|
491
|
+
if language_name == "python":
|
|
492
|
+
try:
|
|
493
|
+
ast.parse(text or "", filename=workspace_relative(target))
|
|
494
|
+
except SyntaxError as exc:
|
|
495
|
+
issues.append(
|
|
496
|
+
{
|
|
497
|
+
"severity": "error",
|
|
498
|
+
"line": exc.lineno or 0,
|
|
499
|
+
"column": exc.offset or 0,
|
|
500
|
+
"message": exc.msg,
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
elif language_name == "json":
|
|
504
|
+
try:
|
|
505
|
+
json.loads(text or "")
|
|
506
|
+
except json.JSONDecodeError as exc:
|
|
507
|
+
issues.append(
|
|
508
|
+
{
|
|
509
|
+
"severity": "error",
|
|
510
|
+
"line": exc.lineno,
|
|
511
|
+
"column": exc.colno,
|
|
512
|
+
"message": exc.msg,
|
|
513
|
+
}
|
|
514
|
+
)
|
|
515
|
+
else:
|
|
516
|
+
for line_no, line in enumerate((text or "").splitlines(), start=1):
|
|
517
|
+
if "\x00" in line:
|
|
518
|
+
issues.append({"severity": "error", "line": line_no, "column": line.index("\x00") + 1, "message": "NUL byte found"})
|
|
519
|
+
if line.rstrip("\n\r") != line.rstrip():
|
|
520
|
+
issues.append({"severity": "warning", "line": line_no, "column": len(line), "message": "trailing whitespace"})
|
|
521
|
+
if len(issues) >= 20:
|
|
522
|
+
break
|
|
523
|
+
return {
|
|
524
|
+
"ok": not any(issue["severity"] == "error" for issue in issues),
|
|
525
|
+
"path": workspace_relative(target),
|
|
526
|
+
"language": language_name,
|
|
527
|
+
"issues": issues,
|
|
528
|
+
"lint_model": "built_in_lightweight",
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _detect_language(path: Path, language: str = "auto") -> str:
|
|
533
|
+
value = str(language or "auto").strip().lower()
|
|
534
|
+
if value and value != "auto":
|
|
535
|
+
aliases = {"py": "python", "python3": "python", "js": "javascript"}
|
|
536
|
+
return aliases.get(value, value)
|
|
537
|
+
suffix = path.suffix.lower()
|
|
538
|
+
if suffix == ".py":
|
|
539
|
+
return "python"
|
|
540
|
+
if suffix == ".json":
|
|
541
|
+
return "json"
|
|
542
|
+
if suffix in {".md", ".markdown"}:
|
|
543
|
+
return "markdown"
|
|
544
|
+
if suffix in {".txt", ".log"}:
|
|
545
|
+
return "text"
|
|
546
|
+
return "text"
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def search_workspace_files(
|
|
550
|
+
query: str,
|
|
551
|
+
path: str = ".",
|
|
552
|
+
is_regex: bool = False,
|
|
553
|
+
glob: str | None = None,
|
|
554
|
+
case_sensitive: bool = False,
|
|
555
|
+
context_lines: int = 0,
|
|
556
|
+
max_results: int = 100,
|
|
557
|
+
) -> dict[str, Any]:
|
|
558
|
+
"""Search UTF-8 text files in the AgentEngine workspace."""
|
|
559
|
+
|
|
560
|
+
return _gateway().invoke(
|
|
561
|
+
"search_workspace_files",
|
|
562
|
+
_search_workspace_files_impl,
|
|
563
|
+
query,
|
|
564
|
+
path,
|
|
565
|
+
is_regex,
|
|
566
|
+
glob,
|
|
567
|
+
case_sensitive,
|
|
568
|
+
context_lines,
|
|
569
|
+
max_results,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _search_workspace_files_impl(
|
|
574
|
+
query: str,
|
|
575
|
+
path: str = ".",
|
|
576
|
+
is_regex: bool = False,
|
|
577
|
+
glob: str | None = None,
|
|
578
|
+
case_sensitive: bool = False,
|
|
579
|
+
context_lines: int = 0,
|
|
580
|
+
max_results: int = 100,
|
|
581
|
+
) -> dict[str, Any]:
|
|
582
|
+
needle = str(query or "").strip()
|
|
583
|
+
if not needle:
|
|
584
|
+
return {"ok": False, "error_message": "query is required"}
|
|
585
|
+
base = resolve_workspace_path(path)
|
|
586
|
+
if shutil.which("rg") and not context_lines:
|
|
587
|
+
rg_result = _search_with_rg(needle, base, is_regex, glob, case_sensitive, context_lines, max_results)
|
|
588
|
+
if rg_result is not None:
|
|
589
|
+
return _search_payload(query, path, rg_result["results"], rg_result["truncated"], context_lines, "rg")
|
|
590
|
+
candidates = [base] if base.is_file() else [item for item in base.rglob("*") if item.is_file()]
|
|
591
|
+
if glob:
|
|
592
|
+
candidates = [item for item in candidates if fnmatch.fnmatch(workspace_relative(item), glob) or fnmatch.fnmatch(item.name, glob)]
|
|
593
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
|
594
|
+
pattern = re.compile(needle if is_regex else re.escape(needle), flags)
|
|
595
|
+
results = []
|
|
596
|
+
limit = max(1, min(int(max_results or 100), 5000))
|
|
597
|
+
context_limit = max(0, min(int(context_lines or 0), 20))
|
|
598
|
+
truncated = False
|
|
599
|
+
for item in candidates:
|
|
600
|
+
if item.stat().st_size > 1024 * 1024:
|
|
601
|
+
continue
|
|
602
|
+
try:
|
|
603
|
+
lines = item.read_text(encoding="utf-8").splitlines()
|
|
604
|
+
except UnicodeDecodeError:
|
|
605
|
+
continue
|
|
606
|
+
for line_no, line in enumerate(lines, start=1):
|
|
607
|
+
match = pattern.search(line)
|
|
608
|
+
if not match:
|
|
609
|
+
continue
|
|
610
|
+
if len(results) >= limit:
|
|
611
|
+
truncated = True
|
|
612
|
+
break
|
|
613
|
+
before = lines[max(0, line_no - 1 - context_limit) : line_no - 1]
|
|
614
|
+
after = lines[line_no : line_no + context_limit]
|
|
615
|
+
results.append(
|
|
616
|
+
{
|
|
617
|
+
"path": workspace_relative(item),
|
|
618
|
+
"line": line_no,
|
|
619
|
+
"text": line,
|
|
620
|
+
"snippet": line,
|
|
621
|
+
"context_before": before,
|
|
622
|
+
"context_after": after,
|
|
623
|
+
}
|
|
624
|
+
)
|
|
625
|
+
if truncated:
|
|
626
|
+
break
|
|
627
|
+
payload = _search_payload(query, path, results, truncated, context_lines, "python")
|
|
628
|
+
budgeted = budget_tool_output(
|
|
629
|
+
tool_name="search_workspace_files",
|
|
630
|
+
field_name="results_json",
|
|
631
|
+
value=results,
|
|
632
|
+
metadata={"tool_use_id": "search_workspace_files"},
|
|
633
|
+
)
|
|
634
|
+
if budgeted.get("persisted"):
|
|
635
|
+
payload["results_preview"] = budgeted["results_json"]
|
|
636
|
+
payload["persisted_outputs"] = {"results": budgeted["persisted"]}
|
|
637
|
+
payload["results_truncated"] = budgeted["truncated"]
|
|
638
|
+
payload["results_original_chars"] = budgeted["original_chars"]
|
|
639
|
+
payload["results_preview_chars"] = budgeted["preview_chars"]
|
|
640
|
+
return payload
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _search_with_rg(
|
|
644
|
+
needle: str,
|
|
645
|
+
base: Path,
|
|
646
|
+
is_regex: bool,
|
|
647
|
+
glob: str | None,
|
|
648
|
+
case_sensitive: bool,
|
|
649
|
+
context_lines: int,
|
|
650
|
+
max_results: int,
|
|
651
|
+
) -> dict[str, Any] | None:
|
|
652
|
+
command = ["rg", "--line-number", "--color", "never", "--no-heading"]
|
|
653
|
+
if not is_regex:
|
|
654
|
+
command.append("--fixed-strings")
|
|
655
|
+
if not case_sensitive:
|
|
656
|
+
command.append("--ignore-case")
|
|
657
|
+
if glob:
|
|
658
|
+
command.extend(["--glob", glob])
|
|
659
|
+
if context_lines:
|
|
660
|
+
command.extend(["--context", str(max(0, min(int(context_lines), 20)))])
|
|
661
|
+
command.extend([needle, str(base)])
|
|
662
|
+
try:
|
|
663
|
+
completed = subprocess.run(command, capture_output=True, text=True, timeout=10, check=False)
|
|
664
|
+
except Exception:
|
|
665
|
+
return None
|
|
666
|
+
if completed.returncode not in {0, 1}:
|
|
667
|
+
return None
|
|
668
|
+
results = []
|
|
669
|
+
limit = max(1, min(int(max_results or 100), 5000))
|
|
670
|
+
truncated = False
|
|
671
|
+
for raw_line in completed.stdout.splitlines():
|
|
672
|
+
separator = ":" if ":" in raw_line else "-"
|
|
673
|
+
parts = raw_line.split(separator, 2)
|
|
674
|
+
if len(parts) < 3 or not parts[1].isdigit():
|
|
675
|
+
continue
|
|
676
|
+
path = Path(parts[0])
|
|
677
|
+
try:
|
|
678
|
+
relative = workspace_relative(path)
|
|
679
|
+
except ValueError:
|
|
680
|
+
continue
|
|
681
|
+
if len(results) >= limit:
|
|
682
|
+
truncated = True
|
|
683
|
+
break
|
|
684
|
+
results.append(
|
|
685
|
+
{
|
|
686
|
+
"path": relative,
|
|
687
|
+
"line": int(parts[1]),
|
|
688
|
+
"text": parts[2],
|
|
689
|
+
"snippet": parts[2],
|
|
690
|
+
"context_before": [],
|
|
691
|
+
"context_after": [],
|
|
692
|
+
}
|
|
693
|
+
)
|
|
694
|
+
return {"ok": True, "query": needle, "results": results, "truncated": truncated}
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _search_payload(
|
|
698
|
+
query: str,
|
|
699
|
+
path: str,
|
|
700
|
+
results: list[dict[str, Any]],
|
|
701
|
+
truncated: bool,
|
|
702
|
+
context_lines: int,
|
|
703
|
+
backend: str,
|
|
704
|
+
) -> dict[str, Any]:
|
|
705
|
+
return {
|
|
706
|
+
"ok": True,
|
|
707
|
+
"query": query,
|
|
708
|
+
"searched_path": path,
|
|
709
|
+
"results": results,
|
|
710
|
+
"match_count": len(results),
|
|
711
|
+
"truncated": truncated,
|
|
712
|
+
"context_lines": max(0, min(int(context_lines or 0), 20)),
|
|
713
|
+
"search_backend": backend,
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _validate_read_state(target: Path, relative: str) -> dict[str, Any] | None:
|
|
718
|
+
read_state = get_read_state(relative)
|
|
719
|
+
if read_state is None:
|
|
720
|
+
return {
|
|
721
|
+
"ok": False,
|
|
722
|
+
"path": relative,
|
|
723
|
+
"error_type": "file_not_read",
|
|
724
|
+
"error_message": "file must be read before editing",
|
|
725
|
+
"suggested_action": f"Call read_workspace_file(path={relative!r}) before editing.",
|
|
726
|
+
}
|
|
727
|
+
stat = target.stat() if target.exists() else None
|
|
728
|
+
if stat is None or stat.st_mtime_ns != read_state.mtime_ns or stat.st_size != read_state.size_bytes:
|
|
729
|
+
return {
|
|
730
|
+
"ok": False,
|
|
731
|
+
"path": relative,
|
|
732
|
+
"error_type": "file_modified_since_read",
|
|
733
|
+
"error_message": "workspace file changed since last read",
|
|
734
|
+
"suggested_action": f"Re-read the file with read_workspace_file(path={relative!r}) and retry.",
|
|
735
|
+
}
|
|
736
|
+
return None
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _apply_single_edit(
|
|
740
|
+
text: str,
|
|
741
|
+
old_text: Any,
|
|
742
|
+
new_text: Any,
|
|
743
|
+
*,
|
|
744
|
+
expected_replacements: int = 1,
|
|
745
|
+
replace_all: bool = False,
|
|
746
|
+
relative: str,
|
|
747
|
+
) -> dict[str, Any]:
|
|
748
|
+
request_error = _validate_single_edit_request(old_text, relative=relative)
|
|
749
|
+
if request_error is not None:
|
|
750
|
+
return request_error
|
|
751
|
+
old = str(old_text)
|
|
752
|
+
expected = max(1, int(expected_replacements or 1))
|
|
753
|
+
actual_old, used_quote_normalization = _find_actual_string(text, old)
|
|
754
|
+
matches = _match_previews(text, actual_old or old) if actual_old else []
|
|
755
|
+
occurrences = text.count(actual_old) if actual_old else 0
|
|
756
|
+
if occurrences == 0:
|
|
757
|
+
return {
|
|
758
|
+
"ok": False,
|
|
759
|
+
"path": relative,
|
|
760
|
+
"error_type": "snippet_not_found",
|
|
761
|
+
"error_message": "old_text was not found in the workspace file",
|
|
762
|
+
"occurrences": 0,
|
|
763
|
+
"nearby_candidates": _nearby_candidates(text, old),
|
|
764
|
+
"suggested_action": "Use one nearby candidate as old_text, or call read_workspace_file with line numbers and retry.",
|
|
765
|
+
}
|
|
766
|
+
if not replace_all and occurrences != expected:
|
|
767
|
+
return {
|
|
768
|
+
"ok": False,
|
|
769
|
+
"path": relative,
|
|
770
|
+
"error_type": "ambiguous_edit",
|
|
771
|
+
"error_message": f"old_text matched {occurrences} occurrences, expected {expected}",
|
|
772
|
+
"occurrences": occurrences,
|
|
773
|
+
"expected_replacements": expected,
|
|
774
|
+
"matches": matches,
|
|
775
|
+
"suggested_action": "Provide a larger unique old_text snippet, set expected_replacements, or use replace_all=true.",
|
|
776
|
+
}
|
|
777
|
+
replacements = occurrences if replace_all else expected
|
|
778
|
+
return {
|
|
779
|
+
"ok": True,
|
|
780
|
+
"updated": text.replace(actual_old, str(new_text or ""), replacements),
|
|
781
|
+
"replacements": replacements,
|
|
782
|
+
"used_quote_normalization": used_quote_normalization,
|
|
783
|
+
"matches": matches,
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _validate_single_edit_request(old_text: Any, *, relative: str) -> dict[str, Any] | None:
|
|
788
|
+
if not str(old_text or ""):
|
|
789
|
+
return {
|
|
790
|
+
"ok": False,
|
|
791
|
+
"path": relative,
|
|
792
|
+
"error_type": "missing_old_text",
|
|
793
|
+
"error_message": "old_text is required",
|
|
794
|
+
}
|
|
795
|
+
return None
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _match_previews(text: str, needle: str, limit: int = 20) -> list[dict[str, Any]]:
|
|
799
|
+
if not needle:
|
|
800
|
+
return []
|
|
801
|
+
matches: list[dict[str, Any]] = []
|
|
802
|
+
for line_no, line in enumerate(text.splitlines(), start=1):
|
|
803
|
+
if needle in line:
|
|
804
|
+
matches.append({"line": line_no, "text": line.strip(), "snippet": line})
|
|
805
|
+
if len(matches) >= limit:
|
|
806
|
+
break
|
|
807
|
+
return matches
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def _nearby_candidates(text: str, old: str, limit: int = 5) -> list[dict[str, Any]]:
|
|
811
|
+
old_norm = _normalize_quotes(old).strip()
|
|
812
|
+
candidates = []
|
|
813
|
+
for line_no, line in enumerate(text.splitlines(), start=1):
|
|
814
|
+
ratio = difflib.SequenceMatcher(None, old_norm, _normalize_quotes(line).strip()).ratio()
|
|
815
|
+
if old_norm and (old_norm[: max(1, min(4, len(old_norm)))] in _normalize_quotes(line) or ratio > 0.45):
|
|
816
|
+
candidates.append({"line": line_no, "text": line.strip(), "snippet": line, "score": round(ratio, 3)})
|
|
817
|
+
candidates.sort(key=lambda item: (-float(item["score"]), int(item["line"])))
|
|
818
|
+
return candidates[:limit]
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def _build_diff(relative: str, before: str, after: str) -> str:
|
|
822
|
+
return "".join(
|
|
823
|
+
difflib.unified_diff(
|
|
824
|
+
before.splitlines(keepends=True),
|
|
825
|
+
after.splitlines(keepends=True),
|
|
826
|
+
fromfile=f"{relative} before",
|
|
827
|
+
tofile=f"{relative} after",
|
|
828
|
+
)
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _budget_diff(tool_name: str, diff: str, relative: str) -> dict[str, Any]:
|
|
833
|
+
return budget_tool_output(
|
|
834
|
+
tool_name=tool_name,
|
|
835
|
+
field_name="diff",
|
|
836
|
+
value=diff,
|
|
837
|
+
metadata={"tool_use_id": f"{tool_name}_{relative.replace('/', '_')}"},
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _refresh_read_state(relative: str, stat: Any, text: str) -> None:
|
|
842
|
+
lines = text.splitlines()
|
|
843
|
+
record_read_state(
|
|
844
|
+
WorkspaceReadState(
|
|
845
|
+
path=relative,
|
|
846
|
+
mtime_ns=stat.st_mtime_ns,
|
|
847
|
+
size_bytes=stat.st_size,
|
|
848
|
+
start_line=1,
|
|
849
|
+
end_line=len(lines),
|
|
850
|
+
total_lines=len(lines),
|
|
851
|
+
partial=False,
|
|
852
|
+
)
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def _find_actual_string(text: str, old: str) -> tuple[str | None, bool]:
|
|
857
|
+
if old in text:
|
|
858
|
+
return old, False
|
|
859
|
+
normalized_old = _normalize_quotes(old)
|
|
860
|
+
for candidate in {normalized_old, old.translate(str.maketrans({'"': "“", "'": "‘"}))}:
|
|
861
|
+
if candidate and candidate in text:
|
|
862
|
+
return candidate, True
|
|
863
|
+
normalized_text = _normalize_quotes(text)
|
|
864
|
+
index = normalized_text.find(normalized_old)
|
|
865
|
+
if index < 0:
|
|
866
|
+
return None, False
|
|
867
|
+
return text[index : index + len(old)], True
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _normalize_quotes(value: str) -> str:
|
|
871
|
+
return value.translate(
|
|
872
|
+
str.maketrans(
|
|
873
|
+
{
|
|
874
|
+
"“": '"',
|
|
875
|
+
"”": '"',
|
|
876
|
+
"„": '"',
|
|
877
|
+
"‘": "'",
|
|
878
|
+
"’": "'",
|
|
879
|
+
"‚": "'",
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def delete_workspace_file(path: str, approval: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
886
|
+
"""Delete a file or empty directory inside the AgentEngine workspace."""
|
|
887
|
+
|
|
888
|
+
return _gateway().invoke("delete_workspace_file", _delete_workspace_file_impl, path, approval=approval)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _delete_workspace_file_impl(path: str) -> dict[str, Any]:
|
|
892
|
+
target = resolve_workspace_path(path)
|
|
893
|
+
root = workspace_root().resolve()
|
|
894
|
+
if target == root:
|
|
895
|
+
return {"ok": False, "error_message": "refuse to delete workspace root"}
|
|
896
|
+
if not target.exists():
|
|
897
|
+
return {"ok": False, "error_message": f"workspace path not found: {path}"}
|
|
898
|
+
if target.is_dir():
|
|
899
|
+
target.rmdir()
|
|
900
|
+
else:
|
|
901
|
+
target.unlink()
|
|
902
|
+
return {"ok": True, "deleted": path}
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def get_workspace_tools() -> list:
|
|
906
|
+
return [
|
|
907
|
+
as_tool(workspace_status),
|
|
908
|
+
as_tool(list_workspace_files),
|
|
909
|
+
as_tool(read_workspace_file),
|
|
910
|
+
as_tool(write_workspace_file),
|
|
911
|
+
as_tool(write_workspace_files),
|
|
912
|
+
as_tool(edit_workspace_file),
|
|
913
|
+
as_tool(multi_edit_workspace_file),
|
|
914
|
+
as_tool(lint_workspace_file),
|
|
915
|
+
as_tool(search_workspace_files),
|
|
916
|
+
as_tool(delete_workspace_file),
|
|
917
|
+
]
|