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,985 @@
1
+ """
2
+ LangGraphRunner - LangGraph 框架运行时
3
+
4
+ 直接透传 LangGraph 原生能力,最小化封装
5
+ """
6
+
7
+ import os
8
+ import uuid
9
+ import re
10
+ from typing import Any, AsyncIterator, Dict, Mapping
11
+ import base64
12
+ from pathlib import Path
13
+
14
+ from ksadk.runners.base_runner import BaseRunner
15
+ from ksadk.runners.usage_accumulator import accumulate_usage
16
+ from ksadk.sessions.continuity import LangGraphSessionAdapter
17
+ from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
18
+ from langgraph.types import Command
19
+ from ksadk.conversations.attachments import classify_attachment_kind, read_attachment_uri_bytes
20
+ from ksadk.conversations.reasoning_markup import ReasoningMarkupParser, strip_reasoning_markup
21
+
22
+
23
+ class LangGraphRunner(BaseRunner):
24
+ """LangGraph 框架运行时
25
+
26
+ 透传原生 LangGraph 功能,支持任意 State 格式
27
+ """
28
+
29
+ def load_agent(self) -> None:
30
+ self._load_agent(force_reload=False)
31
+
32
+ def _load_agent(self, *, force_reload: bool) -> None:
33
+ """加载 LangGraph 编译后的图"""
34
+ self._agent, self._module = load_agent_module(
35
+ self.project_dir,
36
+ self.detection_result.entry_point,
37
+ self.detection_result.agent_variable,
38
+ force_reload=force_reload,
39
+ )
40
+ self._loaded_model_name = self.normalize_requested_model(
41
+ os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
42
+ )
43
+
44
+ if not hasattr(self._agent, "invoke"):
45
+ raise TypeError("加载的对象不是有效的 LangGraph CompiledGraph")
46
+
47
+ def prepare_for_request(self, model: str | None) -> None:
48
+ normalized = self.sync_process_model_env(model)
49
+ if normalized is None or self._agent is None:
50
+ return
51
+ if normalized == getattr(self, "_loaded_model_name", None):
52
+ return
53
+ self._load_agent(force_reload=True)
54
+
55
+ def get_session_adapter(self):
56
+ return LangGraphSessionAdapter()
57
+
58
+ def describe_checkpoint_capability(self) -> dict[str, Any]:
59
+ agent = getattr(self, "_agent", None)
60
+ has_checkpointer = bool(
61
+ getattr(agent, "checkpointer", None) or getattr(agent, "_checkpointer", None)
62
+ )
63
+ if not has_checkpointer:
64
+ return {
65
+ "Supported": False,
66
+ "Backend": "none",
67
+ "Scope": "unknown",
68
+ "Durable": False,
69
+ "SharedAcrossPods": False,
70
+ "Reason": "LangGraph graph has no configured checkpointer",
71
+ }
72
+
73
+ backend = str(os.getenv("KSADK_CHECKPOINT_BACKEND") or "").strip().lower()
74
+ if backend == "local":
75
+ backend = "sqlite"
76
+ if not backend:
77
+ backend = "unknown"
78
+ scope = "unknown"
79
+ durable = False
80
+ shared = False
81
+ reason = ""
82
+ if backend == "postgres":
83
+ scope = "shared"
84
+ durable = True
85
+ shared = True
86
+ elif backend == "sqlite":
87
+ scope = "pod_local"
88
+ durable = True
89
+ shared = False
90
+ reason = "SQLite checkpoint is durable for local web debugging but is not shared across pods"
91
+ elif backend in {"memory", "inmemory"}:
92
+ backend = "memory"
93
+ scope = "process_local"
94
+ durable = False
95
+ shared = False
96
+ reason = "In-memory checkpoint cannot be recovered after process restart or across pods"
97
+
98
+ return {
99
+ "Supported": True,
100
+ "Backend": backend,
101
+ "Scope": scope,
102
+ "Durable": durable,
103
+ "SharedAcrossPods": shared,
104
+ "ResumeMode": "time_travel",
105
+ "Reason": reason,
106
+ }
107
+
108
+ def get_runtime_capabilities(self) -> dict[str, Any]:
109
+ capabilities = super().get_runtime_capabilities()
110
+ capabilities["SessionContinuity"] = {
111
+ "Supported": True,
112
+ "Type": "checkpoint"
113
+ if capabilities["Checkpoint"].get("Supported")
114
+ else "semantic_replay",
115
+ "Level": "runtime"
116
+ if capabilities["Checkpoint"].get("Supported")
117
+ else "semantic",
118
+ "Reason": "",
119
+ }
120
+ return capabilities
121
+
122
+ def _get_config(self, session_id: str) -> dict:
123
+ """获取运行配置"""
124
+ config = {"configurable": {"thread_id": session_id}}
125
+
126
+ langfuse_callbacks = get_langfuse_callbacks()
127
+ if langfuse_callbacks:
128
+ config["callbacks"] = langfuse_callbacks
129
+ config["metadata"] = get_langfuse_metadata(session_id)
130
+
131
+ return config
132
+
133
+ @staticmethod
134
+ def _extract_langgraph_checkpoint_ref(payload: Dict[str, Any]) -> dict[str, Any]:
135
+ framework_ref = payload.get("framework_ref") or {}
136
+ if not isinstance(framework_ref, dict):
137
+ return {}
138
+ langgraph_ref = framework_ref.get("langgraph") or {}
139
+ if not isinstance(langgraph_ref, dict):
140
+ return {}
141
+ return dict(langgraph_ref)
142
+
143
+ @classmethod
144
+ def _apply_checkpoint_resume_config(
145
+ cls,
146
+ config: dict[str, Any],
147
+ *,
148
+ session_id: str,
149
+ checkpoint_ref: dict[str, Any],
150
+ ) -> dict[str, Any]:
151
+ checkpoint_id = str(checkpoint_ref.get("checkpoint_id") or "").strip()
152
+ if not checkpoint_id:
153
+ raise ValueError("checkpoint_resume requires framework_ref.langgraph.checkpoint_id")
154
+
155
+ thread_id = str(checkpoint_ref.get("thread_id") or session_id or "").strip()
156
+ if not thread_id:
157
+ raise ValueError("checkpoint_resume requires session_id or framework_ref.langgraph.thread_id")
158
+
159
+ next_config = dict(config)
160
+ configurable = dict(next_config.get("configurable") or {})
161
+ configurable["thread_id"] = thread_id
162
+ configurable["checkpoint_ns"] = str(checkpoint_ref.get("checkpoint_ns") or "")
163
+ configurable["checkpoint_id"] = checkpoint_id
164
+ next_config["configurable"] = configurable
165
+ return next_config
166
+
167
+ @staticmethod
168
+ def _checkpoint_ref_from_state(state: Any) -> dict[str, Any]:
169
+ state_config = None
170
+ if isinstance(state, dict):
171
+ state_config = state.get("config")
172
+ else:
173
+ state_config = getattr(state, "config", None)
174
+ if not isinstance(state_config, dict):
175
+ return {}
176
+ configurable = state_config.get("configurable") or {}
177
+ if not isinstance(configurable, dict):
178
+ return {}
179
+ thread_id = str(configurable.get("thread_id") or "").strip()
180
+ checkpoint_id = str(configurable.get("checkpoint_id") or "").strip()
181
+ if not thread_id or not checkpoint_id:
182
+ return {}
183
+ next_nodes_raw = state.get("next") if isinstance(state, dict) else getattr(state, "next", None)
184
+ next_nodes: list[str] = []
185
+ if isinstance(next_nodes_raw, str):
186
+ if next_nodes_raw.strip():
187
+ next_nodes = [next_nodes_raw.strip()]
188
+ elif isinstance(next_nodes_raw, (list, tuple, set)):
189
+ next_nodes = [
190
+ str(item).strip()
191
+ for item in next_nodes_raw
192
+ if str(item or "").strip()
193
+ ]
194
+ return {
195
+ "langgraph": {
196
+ "thread_id": thread_id,
197
+ **(
198
+ {"checkpoint_ns": str(configurable.get("checkpoint_ns")).strip()}
199
+ if str(configurable.get("checkpoint_ns") or "").strip()
200
+ else {}
201
+ ),
202
+ "checkpoint_id": checkpoint_id,
203
+ **({"next_node": next_nodes[0], "next_nodes": next_nodes} if next_nodes else {}),
204
+ }
205
+ }
206
+
207
+ async def _latest_checkpoint_metadata(self, config: dict[str, Any]) -> dict[str, Any]:
208
+ state = None
209
+ try:
210
+ if callable(getattr(self._agent, "aget_state", None)):
211
+ state = await self._agent.aget_state(config)
212
+ elif callable(getattr(self._agent, "get_state", None)):
213
+ state = self._agent.get_state(config)
214
+ except Exception:
215
+ return {}
216
+ framework_ref = self._checkpoint_ref_from_state(state)
217
+ if not framework_ref:
218
+ return {}
219
+ langgraph_ref = framework_ref.get("langgraph") if isinstance(framework_ref, dict) else {}
220
+ next_node = ""
221
+ if isinstance(langgraph_ref, dict):
222
+ next_node = str(langgraph_ref.get("next_node") or "").strip()
223
+ checkpoint_capability = self.describe_checkpoint_capability()
224
+ is_terminal = not bool(next_node)
225
+ is_resumable = bool(
226
+ checkpoint_capability.get("Supported")
227
+ and next_node
228
+ and checkpoint_capability.get("Scope") != "process_local"
229
+ )
230
+ return {
231
+ "agentengine": {
232
+ "framework": "langgraph",
233
+ "framework_ref": framework_ref,
234
+ "next_node": next_node,
235
+ "is_terminal": is_terminal,
236
+ "is_resumable": is_resumable,
237
+ "resume_status": "resumable" if is_resumable else "disabled",
238
+ "resume_disabled_reason": (
239
+ "该 checkpoint 已是终态;可选择更早恢复点重跑"
240
+ if is_terminal
241
+ else str(checkpoint_capability.get("Reason") or "")
242
+ ),
243
+ "backend": checkpoint_capability.get("Backend"),
244
+ "scope": checkpoint_capability.get("Scope"),
245
+ "durable": bool(checkpoint_capability.get("Durable")),
246
+ }
247
+ }
248
+
249
+ async def _latest_state_usage(self, config: dict[str, Any]) -> dict[str, Any]:
250
+ state = None
251
+ try:
252
+ if callable(getattr(self._agent, "aget_state", None)):
253
+ state = await self._agent.aget_state(config)
254
+ elif callable(getattr(self._agent, "get_state", None)):
255
+ state = self._agent.get_state(config)
256
+ except Exception:
257
+ return {}
258
+ values = getattr(state, "values", None)
259
+ if values is not None:
260
+ return self._extract_usage(values)
261
+ return {}
262
+
263
+ @staticmethod
264
+ def _latest_checkpoint_config(config: dict[str, Any]) -> dict[str, Any]:
265
+ """Select the newest state in a resumed thread, not the source checkpoint."""
266
+ latest_config = dict(config)
267
+ configurable = dict(latest_config.get("configurable") or {})
268
+ configurable.pop("checkpoint_id", None)
269
+ latest_config["configurable"] = configurable
270
+ return latest_config
271
+
272
+ @staticmethod
273
+ def _ambient_context_text(payload: Dict[str, Any]) -> str:
274
+ sections: list[str] = []
275
+ kb_context = payload.get("kb_context") or {}
276
+ kb_text = str(kb_context.get("formatted_text") or "").strip() if isinstance(kb_context, dict) else ""
277
+ if kb_text:
278
+ sections.append(f"Knowledge base context:\n{kb_text}")
279
+
280
+ memory_context = payload.get("memory_context") or {}
281
+ memory_text = (
282
+ str(memory_context.get("formatted_text") or "").strip()
283
+ if isinstance(memory_context, dict)
284
+ else ""
285
+ )
286
+ if memory_text:
287
+ sections.append(f"Long-term memory context:\n{memory_text}")
288
+
289
+ return "\n\n".join(section for section in sections if section)
290
+
291
+ @staticmethod
292
+ def _strip_platform_context_fields(payload: Dict[str, Any]) -> Dict[str, Any]:
293
+ return {
294
+ key: value
295
+ for key, value in payload.items()
296
+ if key not in {"platform_context", "kb_context", "memory_context"}
297
+ }
298
+
299
+ def _has_prepare_state_hook(self) -> bool:
300
+ module = getattr(self, "_module", None)
301
+ return callable(getattr(module, "ksadk_prepare_state", None))
302
+
303
+ def _prepare_state_with_hook(
304
+ self,
305
+ payload: Dict[str, Any],
306
+ session_id: str,
307
+ history: list,
308
+ *,
309
+ is_resume: bool = False,
310
+ ) -> Dict[str, Any]:
311
+ module = getattr(self, "_module", None)
312
+ prepare_state = getattr(module, "ksadk_prepare_state", None)
313
+ if not callable(prepare_state):
314
+ return self._to_state(payload, history)
315
+
316
+ normalized_payload = self._strip_platform_context_fields(payload)
317
+ session_context = {
318
+ "session_id": session_id,
319
+ "history": list(history),
320
+ "is_resume": bool(is_resume),
321
+ "platform_context": payload.get("platform_context"),
322
+ "kb_context": payload.get("kb_context"),
323
+ "memory_context": payload.get("memory_context"),
324
+ }
325
+ prepared = prepare_state(dict(normalized_payload), session_context)
326
+ if not isinstance(prepared, dict):
327
+ raise TypeError("ksadk_prepare_state(payload, session_context) must return a dict")
328
+ return prepared
329
+
330
+ def _to_state(self, payload: Dict[str, Any], history: list) -> Dict[str, Any]:
331
+ """将简化输入转换为 state,并保留除 input 外的附加字段。"""
332
+ normalized_payload = self._strip_platform_context_fields(payload)
333
+ ambient_text = self._ambient_context_text(payload)
334
+ instructions = str(normalized_payload.pop("instructions", "") or "").strip()
335
+ system_sections = [section for section in (instructions, ambient_text) if section]
336
+ system_text = "\n\n".join(system_sections)
337
+
338
+ if "input" in normalized_payload and "messages" not in normalized_payload:
339
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
340
+
341
+ messages = []
342
+ if system_text:
343
+ messages.append(SystemMessage(content=system_text))
344
+ for msg in history:
345
+ role = msg.get("role")
346
+ content = msg.get("content", "")
347
+ if role == "user":
348
+ messages.append(HumanMessage(content=content))
349
+ elif role in ("assistant", "model"):
350
+ messages.append(AIMessage(content=content))
351
+
352
+ user_input = normalized_payload["input"] or "[empty message]"
353
+ attachments = list(normalized_payload.get("attachments") or [])
354
+ model_metadata = normalized_payload.get("model_metadata")
355
+ user_content = self._build_langgraph_human_content(
356
+ user_input,
357
+ attachments,
358
+ model_metadata=model_metadata if isinstance(model_metadata, dict) else None,
359
+ )
360
+ if not self._history_tail_matches_user_content(history, user_content):
361
+ messages.append(HumanMessage(content=user_content))
362
+ state = {k: v for k, v in normalized_payload.items() if k != "input"}
363
+ state["messages"] = messages
364
+ return state
365
+
366
+ if "messages" in normalized_payload:
367
+ state = dict(normalized_payload)
368
+ if system_text and isinstance(state.get("messages"), list):
369
+ from langchain_core.messages import SystemMessage
370
+
371
+ state["messages"] = [SystemMessage(content=system_text), *state["messages"]]
372
+ return state
373
+
374
+ return normalized_payload
375
+
376
+ @classmethod
377
+ def _history_tail_matches_user_content(cls, history: list, user_content: Any) -> bool:
378
+ if not history:
379
+ return False
380
+ tail = history[-1]
381
+ if not isinstance(tail, dict) or tail.get("role") != "user":
382
+ return False
383
+ tail_text = cls._normalizable_text_content(tail.get("content"))
384
+ user_text = cls._normalizable_text_content(user_content)
385
+ return tail_text is not None and tail_text == user_text
386
+
387
+ @staticmethod
388
+ def _normalizable_text_content(content: Any) -> str | None:
389
+ if isinstance(content, str):
390
+ return content.strip()
391
+ if isinstance(content, list):
392
+ text_parts: list[str] = []
393
+ for item in content:
394
+ if not isinstance(item, dict) or item.get("type") != "text":
395
+ return None
396
+ text_parts.append(str(item.get("text") or ""))
397
+ return "\n".join(text_parts).strip()
398
+ return None
399
+
400
+ @staticmethod
401
+ def _build_langgraph_human_content(
402
+ user_input: str,
403
+ attachments: list[dict[str, Any]],
404
+ *,
405
+ model_metadata: dict[str, Any] | None,
406
+ ) -> Any:
407
+ del model_metadata
408
+ image_blocks: list[dict[str, Any]] = []
409
+
410
+ for attachment in attachments or []:
411
+ if not isinstance(attachment, dict):
412
+ continue
413
+
414
+ mime_type = str(attachment.get("mime_type") or "application/octet-stream")
415
+ display_name = str(attachment.get("display_name") or "")
416
+ if classify_attachment_kind(mime_type, display_name) != "image":
417
+ continue
418
+
419
+ data_b64 = str(attachment.get("data") or "").strip()
420
+ transport = str(attachment.get("transport") or "")
421
+ file_uri = str(attachment.get("file_uri") or "").strip()
422
+
423
+ if transport == "inline" and data_b64:
424
+ image_blocks.append(
425
+ {
426
+ "type": "image_url",
427
+ "image_url": {
428
+ "url": f"data:{mime_type};base64,{data_b64}",
429
+ },
430
+ }
431
+ )
432
+ continue
433
+
434
+ if file_uri.startswith(("http://", "https://")):
435
+ image_blocks.append(
436
+ {
437
+ "type": "image_url",
438
+ "image_url": {
439
+ "url": file_uri,
440
+ },
441
+ }
442
+ )
443
+ continue
444
+
445
+ file_uri = attachment.get("file_uri")
446
+ if not file_uri:
447
+ continue
448
+
449
+ raw = read_attachment_uri_bytes(file_uri)
450
+ if not raw:
451
+ continue
452
+
453
+ image_blocks.append(
454
+ {
455
+ "type": "image_url",
456
+ "image_url": {
457
+ "url": f"data:{mime_type};base64,{base64.b64encode(raw).decode('ascii')}",
458
+ },
459
+ }
460
+ )
461
+
462
+ if not image_blocks:
463
+ return user_input
464
+
465
+ content: list[dict[str, Any]] = []
466
+ if user_input:
467
+ content.append({"type": "text", "text": user_input})
468
+ content.extend(image_blocks)
469
+ return content
470
+
471
+ async def _invoke_graph(
472
+ self,
473
+ payload: Any,
474
+ *,
475
+ config: dict[str, Any],
476
+ context: dict[str, Any] | None,
477
+ ) -> Any:
478
+ if hasattr(self._agent, "ainvoke"):
479
+ kwargs = self._build_optional_call_kwargs(
480
+ self._agent.ainvoke,
481
+ config=config,
482
+ context=context,
483
+ )
484
+ return await self._agent.ainvoke(payload, **kwargs)
485
+ kwargs = self._build_optional_call_kwargs(
486
+ self._agent.invoke,
487
+ config=config,
488
+ context=context,
489
+ )
490
+ return self._agent.invoke(payload, **kwargs)
491
+
492
+ async def _invoke_from_stream_events(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
493
+ chunks: list[dict[str, Any]] = []
494
+ accumulated_text = ""
495
+ final_chunk: dict[str, Any] | None = None
496
+ metadata: dict[str, Any] = {}
497
+
498
+ async for chunk in self.stream(input_data):
499
+ chunks.append(dict(chunk))
500
+ chunk_type = chunk.get("type")
501
+ if chunk_type == "text":
502
+ accumulated_text += str(chunk.get("delta") or "")
503
+ elif chunk_type == "final":
504
+ final_chunk = dict(chunk)
505
+ elif chunk_type == "checkpoint":
506
+ chunk_metadata = chunk.get("metadata")
507
+ if isinstance(chunk_metadata, Mapping):
508
+ metadata.update(dict(chunk_metadata))
509
+ elif chunk_type == "interrupt":
510
+ return {
511
+ "type": "interrupt",
512
+ "interrupt_info": chunk.get("interrupt_info"),
513
+ "session_id": chunk.get("session_id") or input_data.get("session_id"),
514
+ "output": (
515
+ chunk.get("interrupt_info", {}).get("message", "需要用户确认")
516
+ if isinstance(chunk.get("interrupt_info"), Mapping)
517
+ else "需要用户确认"
518
+ ),
519
+ "raw": {"chunks": chunks},
520
+ }
521
+
522
+ if final_chunk:
523
+ output_text = str(final_chunk.get("output") or accumulated_text)
524
+ final_metadata = final_chunk.get("metadata")
525
+ if isinstance(final_metadata, Mapping):
526
+ metadata = {**dict(final_metadata), **metadata}
527
+ result: dict[str, Any] = {"output": output_text, "raw": {"chunks": chunks}}
528
+ usage = final_chunk.get("usage")
529
+ if isinstance(usage, Mapping) and usage:
530
+ result["usage"] = dict(usage)
531
+ if metadata:
532
+ result["metadata"] = metadata
533
+ return result
534
+
535
+ return {"output": accumulated_text, "raw": {"chunks": chunks}}
536
+
537
+ async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
538
+ """调用 LangGraph 图
539
+
540
+ 支持两种输入格式:
541
+ 1. 简化格式: {"input": "hello"} - 自动转换为 messages
542
+ 2. 原生格式: {"messages": [...]} 或自定义 State - 直接透传
543
+ """
544
+ payload = dict(input_data)
545
+ force_graph_invoke = bool(payload.pop("_ksadk_force_graph_invoke", False))
546
+ if not force_graph_invoke and hasattr(self._agent, "astream_events"):
547
+ return await self._invoke_from_stream_events(payload)
548
+
549
+ session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
550
+ is_resume = payload.pop("resume", False)
551
+ is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
552
+ checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
553
+ history = payload.pop("history", [])
554
+ native_context = self.build_native_context(payload.get("platform_context"))
555
+ normalized_payload = self._strip_platform_context_fields(payload)
556
+
557
+ config = self._get_config(session_id)
558
+ if is_checkpoint_resume:
559
+ config = self._apply_checkpoint_resume_config(
560
+ config,
561
+ session_id=session_id,
562
+ checkpoint_ref=checkpoint_ref,
563
+ )
564
+
565
+ # 判断输入格式 / resume
566
+ if is_checkpoint_resume:
567
+ state = None
568
+ elif self._has_prepare_state_hook():
569
+ state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
570
+ elif is_resume:
571
+ if "input" in normalized_payload and len(normalized_payload) == 1:
572
+ state = normalized_payload["input"]
573
+ else:
574
+ state = normalized_payload
575
+ else:
576
+ state = self._to_state(payload, history)
577
+
578
+ try:
579
+ if is_checkpoint_resume:
580
+ result = await self._invoke_graph(
581
+ None,
582
+ config=config,
583
+ context=native_context,
584
+ )
585
+ elif is_resume:
586
+ result = await self._invoke_graph(
587
+ Command(resume=state),
588
+ config=config,
589
+ context=native_context,
590
+ )
591
+ else:
592
+ result = await self._invoke_graph(
593
+ state,
594
+ config=config,
595
+ context=native_context,
596
+ )
597
+
598
+ output = {"output": self._extract_output(result), "raw": result}
599
+ usage = self._extract_usage(result)
600
+ if usage:
601
+ output["usage"] = usage
602
+ last_usage = self._extract_last_usage(result)
603
+ if last_usage:
604
+ output.setdefault("metadata", {})["last_usage"] = last_usage
605
+ metadata = await self._latest_checkpoint_metadata(config)
606
+ if metadata:
607
+ output["metadata"] = {**(output.get("metadata") or {}), **metadata}
608
+ return output
609
+
610
+ except Exception as e:
611
+ if "Interrupt" in type(e).__name__:
612
+ interrupt_info = self._get_interrupt_info(self._agent.get_state(config))
613
+ return {
614
+ "type": "interrupt",
615
+ "interrupt_info": interrupt_info,
616
+ "session_id": session_id,
617
+ "output": interrupt_info.get("message", "需要用户确认") if isinstance(interrupt_info, dict) else "需要用户确认",
618
+ }
619
+ raise
620
+
621
+ def _extract_output(self, result: Any) -> str:
622
+ """从结果中提取输出文本"""
623
+ if isinstance(result, dict):
624
+ # 自定义 output 字段是业务显式出参,优先于内部 messages state。
625
+ if "output" in result:
626
+ return result["output"]
627
+ # LangGraph 示例常用 answer 作为最终业务回答字段。
628
+ if "answer" in result:
629
+ return result["answer"]
630
+ # 标准 messages 格式
631
+ if "messages" in result:
632
+ messages = result["messages"]
633
+ if messages:
634
+ last = messages[-1]
635
+ return last.get("content", str(last)) if isinstance(last, dict) else getattr(last, "content", str(last))
636
+ return str(result) if result else ""
637
+
638
+ def _get_interrupt_info(self, state) -> dict:
639
+ """从 state 中获取 interrupt 信息"""
640
+ if hasattr(state, "tasks") and state.tasks:
641
+ for task in state.tasks:
642
+ if hasattr(task, "interrupts") and task.interrupts:
643
+ for intr in task.interrupts:
644
+ if hasattr(intr, "value"):
645
+ return intr.value
646
+ return {}
647
+
648
+ async def _stream_checkpoint_resume_updates(
649
+ self,
650
+ *,
651
+ config: dict[str, Any],
652
+ context: dict[str, Any] | None,
653
+ ) -> AsyncIterator[Dict[str, Any]]:
654
+ if not callable(getattr(self._agent, "astream", None)):
655
+ return
656
+
657
+ kwargs = self._build_optional_call_kwargs(
658
+ self._agent.astream,
659
+ config=config,
660
+ context=context,
661
+ )
662
+ if self._callable_accepts_keyword(self._agent.astream, "stream_mode"):
663
+ kwargs["stream_mode"] = "updates"
664
+
665
+ latest_output: Any = None
666
+ emitted_update = False
667
+ async for update in self._agent.astream(None, **kwargs):
668
+ emitted_update = True
669
+ latest_output = update
670
+ if isinstance(update, Mapping):
671
+ for node_name, node_output in update.items():
672
+ yield {
673
+ "type": "graph_update",
674
+ "node": str(node_name),
675
+ "output": node_output,
676
+ }
677
+ else:
678
+ yield {"type": "graph_update", "output": update}
679
+
680
+ latest_config = self._latest_checkpoint_config(config)
681
+ state_usage = await self._latest_state_usage(latest_config)
682
+ state_output = ""
683
+ try:
684
+ state = None
685
+ if callable(getattr(self._agent, "aget_state", None)):
686
+ state = await self._agent.aget_state(latest_config)
687
+ elif callable(getattr(self._agent, "get_state", None)):
688
+ state = self._agent.get_state(latest_config)
689
+ values = getattr(state, "values", None)
690
+ if values is not None:
691
+ state_output = self._extract_output(values)
692
+ except Exception:
693
+ state_output = ""
694
+ final_output = state_output or self._extract_output(latest_output)
695
+ yield {
696
+ "type": "final",
697
+ "output": final_output,
698
+ **({"usage": state_usage} if state_usage else {}),
699
+ **({"resume_noop": True} if not emitted_update else {}),
700
+ }
701
+
702
+ metadata = await self._latest_checkpoint_metadata(latest_config)
703
+ if metadata:
704
+ yield {"type": "checkpoint", "metadata": metadata}
705
+
706
+ async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
707
+ """流式调用 LangGraph 图"""
708
+ payload = dict(input_data)
709
+ payload.pop("_ksadk_force_graph_invoke", None)
710
+ session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
711
+ history = payload.pop("history", [])
712
+ is_resume = payload.pop("resume", False)
713
+ is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
714
+ checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
715
+ native_context = self.build_native_context(payload.get("platform_context"))
716
+ normalized_payload = self._strip_platform_context_fields(payload)
717
+
718
+ invoke_payload = dict(payload)
719
+ invoke_payload["session_id"] = session_id
720
+ if history:
721
+ invoke_payload["history"] = history
722
+ if is_resume:
723
+ invoke_payload["resume"] = True
724
+ if is_checkpoint_resume:
725
+ invoke_payload["checkpoint_resume"] = True
726
+
727
+ config = self._get_config(session_id)
728
+ if is_checkpoint_resume:
729
+ config = self._apply_checkpoint_resume_config(
730
+ config,
731
+ session_id=session_id,
732
+ checkpoint_ref=checkpoint_ref,
733
+ )
734
+
735
+ if is_checkpoint_resume:
736
+ state = None
737
+ elif self._has_prepare_state_hook():
738
+ state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
739
+ elif is_resume:
740
+ if "input" in normalized_payload and len(normalized_payload) == 1:
741
+ state = normalized_payload["input"]
742
+ else:
743
+ state = normalized_payload
744
+ else:
745
+ state = self._to_state(payload, history)
746
+
747
+ accumulated_text = ""
748
+ accumulated_reasoning = ""
749
+ inline_reasoning_parser = ReasoningMarkupParser()
750
+ emitted_non_text_event = False
751
+ final_output_text = ""
752
+ final_output_usage: dict[str, Any] = {}
753
+ final_output_last_usage: dict[str, Any] = {}
754
+ model_run_usages: dict[str, dict[str, Any]] = {}
755
+ model_run_order: list[str] = []
756
+ stream_usage_run_keys: set[str] = set()
757
+ latest_stream_usage: dict[str, Any] = {}
758
+
759
+ def model_run_key(
760
+ event: Mapping[str, Any],
761
+ *,
762
+ fallback_key: str | None = None,
763
+ ) -> str:
764
+ raw_run_id = event.get("run_id")
765
+ return (
766
+ str(raw_run_id)
767
+ if raw_run_id
768
+ else fallback_key or f"model-event-{len(model_run_order)}"
769
+ )
770
+
771
+ def record_model_usage(
772
+ event: Mapping[str, Any],
773
+ usage: dict[str, Any],
774
+ *,
775
+ fallback_key: str | None = None,
776
+ ) -> None:
777
+ if not usage:
778
+ return
779
+ run_key = model_run_key(event, fallback_key=fallback_key)
780
+ if run_key not in model_run_usages:
781
+ model_run_order.append(run_key)
782
+ model_run_usages[run_key] = dict(usage)
783
+
784
+ def accumulated_model_usage() -> dict[str, Any]:
785
+ if len(model_run_order) == 1:
786
+ return dict(model_run_usages.get(model_run_order[0]) or {})
787
+ usage: dict[str, Any] = {}
788
+ for run_key in model_run_order:
789
+ usage = accumulate_usage(usage, model_run_usages.get(run_key) or {})
790
+ return usage
791
+
792
+ def latest_model_usage() -> dict[str, Any]:
793
+ for run_key in reversed(model_run_order):
794
+ usage = model_run_usages.get(run_key)
795
+ if usage:
796
+ return dict(usage)
797
+ return {}
798
+
799
+ if is_checkpoint_resume and callable(getattr(self._agent, "astream", None)):
800
+ try:
801
+ async for chunk in self._stream_checkpoint_resume_updates(
802
+ config=config,
803
+ context=native_context,
804
+ ):
805
+ yield chunk
806
+ return
807
+ except Exception as e:
808
+ yield {
809
+ "type": "error",
810
+ "message": str(e) or "LangGraph checkpoint resume failed",
811
+ "checkpoint_id": str(checkpoint_ref.get("checkpoint_id") or ""),
812
+ "exception_type": type(e).__name__,
813
+ }
814
+ return
815
+
816
+ if not hasattr(self._agent, "astream_events"):
817
+ result = await self.invoke(invoke_payload)
818
+ final_chunk = {"output": result.get("output", ""), "type": "final"}
819
+ usage = self._extract_usage(result)
820
+ if usage:
821
+ final_chunk["usage"] = usage
822
+ last_usage = self._extract_last_usage(result)
823
+ if last_usage:
824
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
825
+ yield final_chunk
826
+ return
827
+
828
+ try:
829
+ stream_input = None if is_checkpoint_resume else (Command(resume=state) if is_resume else state)
830
+ stream_kwargs = {"version": "v2", "config": config}
831
+ if native_context and self._callable_accepts_keyword(self._agent.astream_events, "context"):
832
+ stream_kwargs["context"] = native_context
833
+ async for event in self._agent.astream_events(stream_input, **stream_kwargs):
834
+ event_kind = event.get("event", "")
835
+
836
+ if event_kind == "on_chat_model_stream":
837
+ chunk = event.get("data", {}).get("chunk")
838
+ if not chunk:
839
+ continue
840
+ chunk_usage = self._extract_usage(chunk)
841
+ if chunk_usage:
842
+ # Some LangChain providers attach cumulative usage to
843
+ # every stream chunk, and LangChain may then sum those
844
+ # cumulative snapshots into an inflated
845
+ # on_chat_model_end usage. For a concrete model run,
846
+ # keep the latest stream snapshot and ignore the later
847
+ # end usage for that same run_id.
848
+ latest_stream_usage = dict(chunk_usage)
849
+ if event.get("run_id"):
850
+ run_key = model_run_key(event)
851
+ stream_usage_run_keys.add(run_key)
852
+ record_model_usage(event, latest_stream_usage)
853
+
854
+ # 推理内容
855
+ reasoning = getattr(chunk, "reasoning_content", None)
856
+ if not reasoning and hasattr(chunk, "additional_kwargs"):
857
+ reasoning = chunk.additional_kwargs.get("reasoning_content")
858
+
859
+ if reasoning:
860
+ accumulated_reasoning += reasoning
861
+ yield {"delta": reasoning, "type": "thinking"}
862
+
863
+ # 常规内容
864
+ if hasattr(chunk, "content") and chunk.content:
865
+ content = self._filter_tool_tags(chunk.content)
866
+ if isinstance(content, str):
867
+ if accumulated_reasoning and content.startswith(accumulated_reasoning):
868
+ content = content[len(accumulated_reasoning):]
869
+ elif reasoning and content.startswith(reasoning):
870
+ content = content[len(reasoning):]
871
+ if content:
872
+ for part in inline_reasoning_parser.feed(content):
873
+ if not part.text or not part.text.strip():
874
+ continue
875
+ if part.kind == "thinking":
876
+ accumulated_reasoning += part.text
877
+ yield {"delta": part.text, "type": "thinking"}
878
+ else:
879
+ accumulated_text += part.text
880
+ yield {"delta": part.text, "type": "text"}
881
+
882
+ elif event_kind == "on_chat_model_end":
883
+ data = event.get("data") or {}
884
+ output = data.get("output") if isinstance(data, Mapping) else None
885
+ usage = self._extract_usage(output) or self._extract_usage(data)
886
+ last_usage = self._extract_last_usage(output) or self._extract_last_usage(data)
887
+ run_key = model_run_key(event)
888
+ if run_key not in stream_usage_run_keys:
889
+ record_model_usage(event, last_usage or usage)
890
+
891
+ elif event_kind == "on_tool_start":
892
+ emitted_non_text_event = True
893
+ yield {
894
+ "type": "tool_call",
895
+ "tool_name": event.get("name", "unknown"),
896
+ "tool_args": event.get("data", {}).get("input", {}),
897
+ "run_id": event.get("run_id"),
898
+ }
899
+
900
+ elif event_kind == "on_tool_end":
901
+ emitted_non_text_event = True
902
+ tool_output = event.get("data", {}).get("output", "")
903
+ yield {
904
+ "type": "tool_result",
905
+ "tool_name": event.get("name", "unknown"),
906
+ "tool_args": event.get("data", {}).get("input", {}),
907
+ "tool_output": tool_output if isinstance(tool_output, dict) else (str(tool_output) if tool_output else ""),
908
+ "run_id": event.get("run_id"),
909
+ }
910
+
911
+ elif event_kind == "on_chain_end":
912
+ output = event.get("data", {}).get("output", {})
913
+ if isinstance(output, dict) and "__interrupt__" in output:
914
+ emitted_non_text_event = True
915
+ yield {"type": "interrupt", "interrupt_info": output["__interrupt__"], "session_id": session_id}
916
+ return
917
+ extracted_output = self._extract_output(output)
918
+ if extracted_output:
919
+ final_output_text = strip_reasoning_markup(str(extracted_output))
920
+ final_output_usage = self._extract_usage(output)
921
+ final_output_last_usage = self._extract_last_usage(output)
922
+
923
+ except Exception as e:
924
+ if "Interrupt" in type(e).__name__:
925
+ yield {"type": "interrupt", "interrupt_info": self._get_interrupt_info(self._agent.get_state(config)), "session_id": session_id}
926
+ return
927
+ raise
928
+
929
+ for part in inline_reasoning_parser.flush():
930
+ if not part.text or not part.text.strip():
931
+ continue
932
+ if part.kind == "thinking":
933
+ accumulated_reasoning += part.text
934
+ yield {"delta": part.text, "type": "thinking"}
935
+ else:
936
+ accumulated_text += part.text
937
+ yield {"delta": part.text, "type": "text"}
938
+
939
+ if not accumulated_text:
940
+ if final_output_text:
941
+ final_chunk = {"output": final_output_text, "type": "final"}
942
+ usage = accumulated_model_usage() or final_output_usage or latest_stream_usage
943
+ last_usage = latest_model_usage() or final_output_last_usage or latest_stream_usage or usage
944
+ if usage:
945
+ final_chunk["usage"] = usage
946
+ if last_usage:
947
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
948
+ yield final_chunk
949
+ elif not emitted_non_text_event:
950
+ result = await self.invoke(
951
+ {**invoke_payload, "_ksadk_force_graph_invoke": True}
952
+ )
953
+ final_chunk = {"output": result.get("output", ""), "type": "final"}
954
+ usage = self._extract_usage(result)
955
+ if usage:
956
+ final_chunk["usage"] = usage
957
+ last_usage = self._extract_last_usage(result)
958
+ if last_usage:
959
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
960
+ yield final_chunk
961
+ metadata = result.get("metadata") if isinstance(result, dict) else None
962
+ if isinstance(metadata, dict) and metadata.get("agentengine"):
963
+ yield {"type": "checkpoint", "metadata": metadata}
964
+ return
965
+ else:
966
+ final_chunk = {"output": accumulated_text, "type": "final"}
967
+ state_usage = await self._latest_state_usage(config)
968
+ usage = accumulated_model_usage() or state_usage or final_output_usage or latest_stream_usage
969
+ if usage:
970
+ final_chunk["usage"] = usage
971
+ last_usage = latest_model_usage() or state_usage or final_output_last_usage or latest_stream_usage or usage
972
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
973
+ yield final_chunk
974
+
975
+ metadata = await self._latest_checkpoint_metadata(config)
976
+ if metadata:
977
+ yield {"type": "checkpoint", "metadata": metadata}
978
+
979
+ def _filter_tool_tags(self, content: str) -> str:
980
+ """过滤 <tool_call> 标签"""
981
+ if not isinstance(content, str):
982
+ return content
983
+ content = re.sub(r'<tool_call>.*?</tool_call>', '', content, flags=re.DOTALL)
984
+ content = re.sub(r'</?(?:tool_call|arg_key|arg_value)>', '', content)
985
+ return content