agentengine-sdk-python 0.6.4__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
  2. agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
  3. {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
  7. ksadk/__init__.py +13 -0
  8. ksadk/__main__.py +4 -0
  9. ksadk/a2a/__init__.py +15 -0
  10. ksadk/a2a/card_builder.py +64 -0
  11. ksadk/a2a/client.py +309 -0
  12. ksadk/a2a/executor.py +201 -0
  13. ksadk/a2a/server.py +79 -0
  14. ksadk/agents/__init__.py +20 -0
  15. ksadk/agents/base.py +370 -0
  16. ksadk/agents/context.py +38 -0
  17. ksadk/agents/event.py +35 -0
  18. ksadk/agents/loop_agent.py +71 -0
  19. ksadk/agents/parallel_agent.py +109 -0
  20. ksadk/agents/runner_adapter.py +24 -0
  21. ksadk/agents/sequential_agent.py +34 -0
  22. ksadk/api/__init__.py +3 -0
  23. ksadk/api/client.py +2246 -0
  24. ksadk/builders/__init__.py +20 -0
  25. ksadk/builders/base.py +66 -0
  26. ksadk/builders/code_builder.py +1990 -0
  27. ksadk/builders/container_builder.py +786 -0
  28. ksadk/builders/framework_requirements.py +59 -0
  29. ksadk/builders/ks3_uploader.py +389 -0
  30. ksadk/builders/mcp_builder.py +476 -0
  31. ksadk/builders/requirements_utils.py +75 -0
  32. ksadk/cli/__init__.py +531 -0
  33. ksadk/cli/__main__.py +4 -0
  34. ksadk/cli/agent_ref.py +204 -0
  35. ksadk/cli/cmd_a2a.py +167 -0
  36. ksadk/cli/cmd_agent.py +201 -0
  37. ksadk/cli/cmd_build.py +270 -0
  38. ksadk/cli/cmd_completion.py +304 -0
  39. ksadk/cli/cmd_config.py +758 -0
  40. ksadk/cli/cmd_create.py +2304 -0
  41. ksadk/cli/cmd_dashboard.py +900 -0
  42. ksadk/cli/cmd_deploy.py +704 -0
  43. ksadk/cli/cmd_destroy.py +345 -0
  44. ksadk/cli/cmd_files.py +1227 -0
  45. ksadk/cli/cmd_hermes.py +1263 -0
  46. ksadk/cli/cmd_invoke.py +1503 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1179 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3922 -0
  51. ksadk/cli/cmd_run.py +239 -0
  52. ksadk/cli/cmd_status.py +588 -0
  53. ksadk/cli/cmd_version.py +522 -0
  54. ksadk/cli/cmd_web.py +288 -0
  55. ksadk/cli/deploy_utils.py +87 -0
  56. ksadk/cli/dry_run.py +214 -0
  57. ksadk/cli/env_options.py +82 -0
  58. ksadk/cli/error_utils.py +525 -0
  59. ksadk/cli/global_options.py +31 -0
  60. ksadk/cli/local_runtime.py +97 -0
  61. ksadk/cli/model_catalog.py +136 -0
  62. ksadk/cli/network_options.py +412 -0
  63. ksadk/cli/resource_common.py +621 -0
  64. ksadk/cli/storage.py +84 -0
  65. ksadk/cli/ui.py +431 -0
  66. ksadk/cli/workflow_common.py +552 -0
  67. ksadk/common/__init__.py +14 -0
  68. ksadk/common/aicp_env.py +128 -0
  69. ksadk/common/auth.py +190 -0
  70. ksadk/common/constants.py +84 -0
  71. ksadk/common/llm_utils.py +61 -0
  72. ksadk/configs/__init__.py +61 -0
  73. ksadk/configs/env_registry.py +343 -0
  74. ksadk/configs/global_config.py +200 -0
  75. ksadk/configs/settings.py +706 -0
  76. ksadk/conversations/__init__.py +95 -0
  77. ksadk/conversations/attachment_storage.py +379 -0
  78. ksadk/conversations/attachments.py +774 -0
  79. ksadk/conversations/compaction_pipeline.py +387 -0
  80. ksadk/conversations/compaction_prompt.py +128 -0
  81. ksadk/conversations/context.py +303 -0
  82. ksadk/conversations/message_projection.py +269 -0
  83. ksadk/conversations/model_context.py +370 -0
  84. ksadk/conversations/model_options.py +136 -0
  85. ksadk/conversations/normalize.py +446 -0
  86. ksadk/conversations/reasoning_markup.py +91 -0
  87. ksadk/conversations/run_kinds.py +61 -0
  88. ksadk/conversations/run_status.py +53 -0
  89. ksadk/conversations/runtime.py +5276 -0
  90. ksadk/conversations/semantic_summary.py +359 -0
  91. ksadk/conversations/session_title.py +254 -0
  92. ksadk/deployment/__init__.py +83 -0
  93. ksadk/deployment/agent_access.py +226 -0
  94. ksadk/deployment/base.py +229 -0
  95. ksadk/deployment/manager.py +332 -0
  96. ksadk/deployment/providers/__init__.py +10 -0
  97. ksadk/deployment/providers/serverless.py +1115 -0
  98. ksadk/deployment/registry.py +120 -0
  99. ksadk/deployment/state.py +78 -0
  100. ksadk/deployment/ui_config.py +21 -0
  101. ksadk/detection/__init__.py +7 -0
  102. ksadk/detection/detector.py +431 -0
  103. ksadk/detection/mcp_detector.py +250 -0
  104. ksadk/hermes_terminal.py +467 -0
  105. ksadk/identity/__init__.py +17 -0
  106. ksadk/identity/resolver.py +385 -0
  107. ksadk/knowledge_base/__init__.py +62 -0
  108. ksadk/knowledge_base/adk_tool.py +47 -0
  109. ksadk/knowledge_base/client.py +297 -0
  110. ksadk/knowledge_base/langchain_tool.py +55 -0
  111. ksadk/knowledge_base/service.py +66 -0
  112. ksadk/knowledge_base/tool.py +50 -0
  113. ksadk/markdown.py +169 -0
  114. ksadk/mcp_runtime/__init__.py +231 -0
  115. ksadk/memory/__init__.py +57 -0
  116. ksadk/memory/adk/__init__.py +51 -0
  117. ksadk/memory/adk/backends/__init__.py +5 -0
  118. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  119. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  120. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  121. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  122. ksadk/memory/adk/long_term_memory.py +273 -0
  123. ksadk/memory/adk/resilient_session_service.py +268 -0
  124. ksadk/memory/adk/short_term_memory.py +304 -0
  125. ksadk/memory/adk_tool.py +31 -0
  126. ksadk/memory/backends/__init__.py +6 -0
  127. ksadk/memory/backends/base.py +103 -0
  128. ksadk/memory/backends/memory.py +112 -0
  129. ksadk/memory/backends/redis.py +146 -0
  130. ksadk/memory/langchain_tool.py +39 -0
  131. ksadk/memory/ltm_backend_factory.py +24 -0
  132. ksadk/memory/manager.py +215 -0
  133. ksadk/memory/service.py +172 -0
  134. ksadk/memory/tool.py +96 -0
  135. ksadk/model_policy.py +223 -0
  136. ksadk/openclaw_gateway.py +378 -0
  137. ksadk/runners/__init__.py +8 -0
  138. ksadk/runners/adk_runner.py +1994 -0
  139. ksadk/runners/base_runner.py +305 -0
  140. ksadk/runners/deepagents_runner.py +13 -0
  141. ksadk/runners/factory.py +73 -0
  142. ksadk/runners/langchain_runner.py +751 -0
  143. ksadk/runners/langgraph_runner.py +985 -0
  144. ksadk/runners/patch_langchain.py +187 -0
  145. ksadk/runners/remote_runner.py +792 -0
  146. ksadk/runners/usage_accumulator.py +38 -0
  147. ksadk/runners/utils/__init__.py +19 -0
  148. ksadk/runners/utils/langfuse.py +230 -0
  149. ksadk/runners/utils/loader.py +62 -0
  150. ksadk/runtime_context.py +166 -0
  151. ksadk/runtime_state.py +25 -0
  152. ksadk/sandbox/__init__.py +28 -0
  153. ksadk/sandbox/backends/__init__.py +9 -0
  154. ksadk/sandbox/backends/e2b.py +154 -0
  155. ksadk/sandbox/backends/local_process.py +131 -0
  156. ksadk/sandbox/base.py +101 -0
  157. ksadk/sandbox/factory.py +53 -0
  158. ksadk/sandbox/registry.py +187 -0
  159. ksadk/server/__init__.py +7 -0
  160. ksadk/server/api_models.py +73 -0
  161. ksadk/server/app.py +3931 -0
  162. ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
  163. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  165. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  166. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  171. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  172. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  177. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  189. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  190. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  195. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  204. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  205. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  207. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  208. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  210. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  213. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  214. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  215. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  218. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  219. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  221. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  222. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
  224. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  225. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
  226. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  227. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
  228. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  229. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  230. ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
  231. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  232. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
  233. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  234. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  235. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  236. ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
  237. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  238. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
  239. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  240. ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
  241. ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
  242. ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
  243. ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
  244. ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
  245. ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
  246. ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
  247. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  248. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  249. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  250. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  251. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  252. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  253. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  254. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  255. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  256. ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
  257. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  258. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  259. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  260. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  261. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  262. ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
  263. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  264. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  265. ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
  266. ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
  267. ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
  268. ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
  269. ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
  270. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  271. ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
  272. ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
  273. ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
  274. ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
  275. ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
  276. ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
  277. ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
  278. ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
  279. ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
  280. ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
  281. ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
  282. ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
  283. ksadk/server/static/assets/dist-D-xOD048.js +1 -0
  284. ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
  285. ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
  286. ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
  287. ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
  288. ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
  289. ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
  290. ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
  291. ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
  292. ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
  293. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  294. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  295. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  296. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  297. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  298. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  299. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  300. ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
  301. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  302. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  303. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  304. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  305. ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
  306. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  307. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  308. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
  309. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  310. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  311. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  312. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
  313. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  314. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  315. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  316. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  317. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  318. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  319. ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
  320. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  321. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  322. ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
  323. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  324. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
  325. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  326. ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
  327. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  328. ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
  329. ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
  330. ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
  331. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  332. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  333. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  334. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  335. ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
  336. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
  337. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  338. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  339. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  340. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  341. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  342. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  343. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  344. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  345. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  346. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  347. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  348. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  349. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  350. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  351. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  352. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
  353. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  354. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  355. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  356. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  357. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  358. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  359. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  360. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  361. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
  362. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  364. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
  365. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  366. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  367. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
  368. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  369. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  370. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
  371. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  372. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  373. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  374. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  375. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  376. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  377. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  378. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  379. ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
  380. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  381. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  382. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  383. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  384. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  385. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  386. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  387. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  388. ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
  389. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  390. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  391. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  392. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  393. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  394. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  395. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  396. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  397. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  398. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  399. ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
  400. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  401. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  402. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
  404. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  405. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  406. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  407. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
  408. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  409. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  410. ksadk/server/static/favicon.svg +1 -0
  411. ksadk/server/static/icons.svg +24 -0
  412. ksadk/server/static/index.html +14 -0
  413. ksadk/server/terminal_sessions.py +551 -0
  414. ksadk/sessions/__init__.py +279 -0
  415. ksadk/sessions/base.py +354 -0
  416. ksadk/sessions/continuity.py +337 -0
  417. ksadk/sessions/errors.py +7 -0
  418. ksadk/sessions/in_memory.py +273 -0
  419. ksadk/sessions/local_service.py +949 -0
  420. ksadk/sessions/postgres_service.py +901 -0
  421. ksadk/sessions/resilience.py +52 -0
  422. ksadk/sessions/resilient.py +360 -0
  423. ksadk/sessions/sqlite_service.py +13 -0
  424. ksadk/skills/__init__.py +9 -0
  425. ksadk/skills/loader.py +46 -0
  426. ksadk/skills/models.py +120 -0
  427. ksadk/skills/package_store.py +104 -0
  428. ksadk/skills/runtime/__init__.py +17 -0
  429. ksadk/skills/runtime/agent.py +183 -0
  430. ksadk/skills/runtime/artifacts.py +39 -0
  431. ksadk/skills/runtime/backends/__init__.py +1 -0
  432. ksadk/skills/runtime/backends/disabled.py +8 -0
  433. ksadk/skills/runtime/backends/e2b.py +168 -0
  434. ksadk/skills/runtime/backends/local.py +95 -0
  435. ksadk/skills/runtime/base.py +103 -0
  436. ksadk/skills/runtime/executor.py +219 -0
  437. ksadk/skills/runtime/factory.py +37 -0
  438. ksadk/skills/runtime/loader.py +131 -0
  439. ksadk/skills/runtime/registry.py +150 -0
  440. ksadk/skills/runtime/request.py +67 -0
  441. ksadk/skills/service_client.py +239 -0
  442. ksadk/skills/service_env.py +58 -0
  443. ksadk/skills/tool_defs.py +221 -0
  444. ksadk/terminal_client.py +69 -0
  445. ksadk/terminal_exec_policy.py +229 -0
  446. ksadk/tools/__init__.py +17 -0
  447. ksadk/tools/gateway.py +230 -0
  448. ksadk/tools/result_budget.py +154 -0
  449. ksadk/toolsets/__init__.py +854 -0
  450. ksadk/toolsets/_langchain.py +11 -0
  451. ksadk/toolsets/platform.py +93 -0
  452. ksadk/toolsets/sandbox.py +310 -0
  453. ksadk/toolsets/skills.py +217 -0
  454. ksadk/toolsets/web.py +266 -0
  455. ksadk/toolsets/workspace.py +917 -0
  456. ksadk/toolsets/workspace_state.py +44 -0
  457. ksadk/tracing/__init__.py +37 -0
  458. ksadk/tracing/exporters/__init__.py +1 -0
  459. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  460. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  461. ksadk/tracing/setup.py +892 -0
  462. ksadk/tracing/span_utils.py +114 -0
  463. ksadk/tui/__init__.py +27 -0
  464. ksadk/tui/loop.py +1877 -0
  465. ksadk/tui/stream_render.py +68 -0
  466. ksadk/ui_config.py +168 -0
  467. ksadk/version.py +4 -0
  468. ksadk_runtime_common/__init__.py +20 -0
  469. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  470. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  471. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  472. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  473. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  474. ksadk_runtime_common/memory_backend/registry.py +56 -0
  475. ksadk_runtime_common/memory_backend/render.py +71 -0
  476. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  477. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  478. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  479. ksadk_runtime_common/workspace_files/constants.py +9 -0
  480. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  481. ksadk_runtime_common/workspace_files/preview.py +96 -0
  482. ksadk_runtime_common/workspace_files/router.py +254 -0
  483. agentengine_sdk_python/__init__.py +0 -3
  484. agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
  485. agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
  486. agentengine_sdk_python-0.6.4.dist-info/top_level.txt +0 -1
@@ -0,0 +1,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
+ ]