agentengine-sdk-python 0.6.2__py3-none-any.whl → 0.6.9__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 (490) hide show
  1. agentengine_sdk_python-0.6.9.dist-info/METADATA +179 -0
  2. agentengine_sdk_python-0.6.9.dist-info/RECORD +487 -0
  3. {agentengine_sdk_python-0.6.2.dist-info → agentengine_sdk_python-0.6.9.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.6.9.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.6.9.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.6.9.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 +2227 -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 +2277 -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 +1259 -0
  46. ksadk/cli/cmd_invoke.py +1336 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1174 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3918 -0
  51. ksadk/cli/cmd_run.py +232 -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 +410 -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 +342 -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/model_context.py +360 -0
  83. ksadk/conversations/model_options.py +136 -0
  84. ksadk/conversations/normalize.py +446 -0
  85. ksadk/conversations/reasoning_markup.py +91 -0
  86. ksadk/conversations/run_kinds.py +61 -0
  87. ksadk/conversations/run_status.py +53 -0
  88. ksadk/conversations/runtime.py +5208 -0
  89. ksadk/conversations/semantic_summary.py +359 -0
  90. ksadk/conversations/session_title.py +254 -0
  91. ksadk/deployment/__init__.py +83 -0
  92. ksadk/deployment/agent_access.py +226 -0
  93. ksadk/deployment/base.py +229 -0
  94. ksadk/deployment/manager.py +332 -0
  95. ksadk/deployment/providers/__init__.py +10 -0
  96. ksadk/deployment/providers/serverless.py +1105 -0
  97. ksadk/deployment/registry.py +120 -0
  98. ksadk/deployment/state.py +78 -0
  99. ksadk/deployment/ui_config.py +21 -0
  100. ksadk/detection/__init__.py +7 -0
  101. ksadk/detection/detector.py +431 -0
  102. ksadk/detection/mcp_detector.py +250 -0
  103. ksadk/hermes_terminal.py +467 -0
  104. ksadk/identity/__init__.py +17 -0
  105. ksadk/identity/resolver.py +385 -0
  106. ksadk/knowledge_base/__init__.py +62 -0
  107. ksadk/knowledge_base/adk_tool.py +47 -0
  108. ksadk/knowledge_base/client.py +297 -0
  109. ksadk/knowledge_base/langchain_tool.py +55 -0
  110. ksadk/knowledge_base/service.py +66 -0
  111. ksadk/knowledge_base/tool.py +50 -0
  112. ksadk/markdown.py +169 -0
  113. ksadk/mcp_runtime/__init__.py +231 -0
  114. ksadk/memory/__init__.py +57 -0
  115. ksadk/memory/adk/__init__.py +51 -0
  116. ksadk/memory/adk/backends/__init__.py +5 -0
  117. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  118. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  119. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  120. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  121. ksadk/memory/adk/long_term_memory.py +273 -0
  122. ksadk/memory/adk/short_term_memory.py +289 -0
  123. ksadk/memory/adk_tool.py +31 -0
  124. ksadk/memory/backends/__init__.py +6 -0
  125. ksadk/memory/backends/base.py +103 -0
  126. ksadk/memory/backends/memory.py +112 -0
  127. ksadk/memory/backends/redis.py +146 -0
  128. ksadk/memory/langchain_tool.py +39 -0
  129. ksadk/memory/ltm_backend_factory.py +24 -0
  130. ksadk/memory/manager.py +215 -0
  131. ksadk/memory/service.py +172 -0
  132. ksadk/memory/tool.py +96 -0
  133. ksadk/model_policy.py +223 -0
  134. ksadk/openclaw_gateway.py +378 -0
  135. ksadk/runners/__init__.py +8 -0
  136. ksadk/runners/adk_runner.py +1135 -0
  137. ksadk/runners/base_runner.py +305 -0
  138. ksadk/runners/deepagents_runner.py +13 -0
  139. ksadk/runners/factory.py +73 -0
  140. ksadk/runners/langchain_runner.py +539 -0
  141. ksadk/runners/langgraph_runner.py +852 -0
  142. ksadk/runners/patch_langchain.py +187 -0
  143. ksadk/runners/remote_runner.py +667 -0
  144. ksadk/runners/usage_accumulator.py +38 -0
  145. ksadk/runners/utils/__init__.py +19 -0
  146. ksadk/runners/utils/langfuse.py +230 -0
  147. ksadk/runners/utils/loader.py +62 -0
  148. ksadk/runtime_context.py +166 -0
  149. ksadk/runtime_state.py +25 -0
  150. ksadk/sandbox/__init__.py +28 -0
  151. ksadk/sandbox/backends/__init__.py +9 -0
  152. ksadk/sandbox/backends/e2b.py +154 -0
  153. ksadk/sandbox/backends/local_process.py +131 -0
  154. ksadk/sandbox/base.py +101 -0
  155. ksadk/sandbox/factory.py +53 -0
  156. ksadk/sandbox/registry.py +187 -0
  157. ksadk/server/__init__.py +7 -0
  158. ksadk/server/api_models.py +73 -0
  159. ksadk/server/app.py +3789 -0
  160. ksadk/server/static/assets/ArtifactsPanel-C0meuOdi.js +1 -0
  161. ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +9 -0
  162. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  163. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  165. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  166. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  171. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  172. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  177. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  189. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  190. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  195. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  204. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  205. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  207. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  208. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  210. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  213. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  214. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  215. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  218. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  219. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  221. ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +8 -0
  222. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  223. ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +303 -0
  224. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +1 -0
  226. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  227. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  228. ksadk/server/static/assets/arc-bj2rZnP5.js +1 -0
  229. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +1 -0
  230. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-2u1ltDIj.js +36 -0
  231. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  232. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  233. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  234. ksadk/server/static/assets/blockDiagram-GPEHLZMM-D6fvdK20.js +132 -0
  235. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  236. ksadk/server/static/assets/c4Diagram-AAUBKEIU-BK82L5n5.js +10 -0
  237. ksadk/server/static/assets/channel-BSSOwFIb.js +1 -0
  238. ksadk/server/static/assets/chunk-2J33WTMH-DLVdkeyQ.js +1 -0
  239. ksadk/server/static/assets/chunk-4BX2VUAB-BbwsYcbW.js +1 -0
  240. ksadk/server/static/assets/chunk-55IACEB6-DOairxzw.js +1 -0
  241. ksadk/server/static/assets/chunk-727SXJPM-hCO487gs.js +206 -0
  242. ksadk/server/static/assets/chunk-AQP2D5EJ-XU8vDOuG.js +231 -0
  243. ksadk/server/static/assets/chunk-FMBD7UC4-PrapyfMu.js +15 -0
  244. ksadk/server/static/assets/chunk-ND2GUHAM-CboKhTZ3.js +1 -0
  245. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +1 -0
  246. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +1 -0
  247. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +1 -0
  248. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  249. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  250. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  251. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  252. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  253. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  254. ksadk/server/static/assets/cose-bilkent-S5V4N54A-_N8bJ-EU.js +1 -0
  255. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  256. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  257. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  258. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  259. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  260. ksadk/server/static/assets/dagre-BM42HDAG-FoDlo5J2.js +4 -0
  261. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  262. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  263. ksadk/server/static/assets/diagram-2AECGRRQ-BBp6ebBw.js +43 -0
  264. ksadk/server/static/assets/diagram-5GNKFQAL-CqOvG1PR.js +10 -0
  265. ksadk/server/static/assets/diagram-KO2AKTUF-O9T3t6ba.js +3 -0
  266. ksadk/server/static/assets/diagram-LMA3HP47-CGG8ekBO.js +24 -0
  267. ksadk/server/static/assets/diagram-OG6HWLK6-4BmH1EEg.js +24 -0
  268. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  269. ksadk/server/static/assets/dist-6kSZvCZh.js +1 -0
  270. ksadk/server/static/assets/dist-B67uF2uK.js +1 -0
  271. ksadk/server/static/assets/dist-BMKM66B4.js +1 -0
  272. ksadk/server/static/assets/dist-BVzcWa6D.js +1 -0
  273. ksadk/server/static/assets/dist-Bk1l05Ni.js +1 -0
  274. ksadk/server/static/assets/dist-BkJjNih3.js +1 -0
  275. ksadk/server/static/assets/dist-BmuTWcm7.js +9 -0
  276. ksadk/server/static/assets/dist-C2DKRFVK.js +1 -0
  277. ksadk/server/static/assets/dist-CWiTDN_V.js +6 -0
  278. ksadk/server/static/assets/dist-CZ6C3x8B.js +23 -0
  279. ksadk/server/static/assets/dist-CiZHB6ZW.js +1 -0
  280. ksadk/server/static/assets/dist-Cp1_jKwf.js +1 -0
  281. ksadk/server/static/assets/dist-Cwhk4lwI.js +2 -0
  282. ksadk/server/static/assets/dist-D5JGcxBx.js +11 -0
  283. ksadk/server/static/assets/dist-D99wf6_X.js +1 -0
  284. ksadk/server/static/assets/dist-DCpZrjaK.js +1 -0
  285. ksadk/server/static/assets/dist-DI-oVAXw.js +1 -0
  286. ksadk/server/static/assets/dist-DLMjL01k.js +1 -0
  287. ksadk/server/static/assets/dist-DLeM1OCK.js +1 -0
  288. ksadk/server/static/assets/dist-DM0rEQPk.js +1 -0
  289. ksadk/server/static/assets/dist-DbaH3p5h.js +1 -0
  290. ksadk/server/static/assets/dist-S4klkK5W.js +1 -0
  291. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  292. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  293. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  294. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  295. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  296. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  297. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  298. ksadk/server/static/assets/erDiagram-TEJ5UH35-oJhaci7S.js +85 -0
  299. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  300. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +1 -0
  301. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  302. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  303. ksadk/server/static/assets/flowDiagram-I6XJVG4X-DtTmUan-.js +162 -0
  304. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  305. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  306. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BSsb_eij.js +292 -0
  307. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  308. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  309. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +1 -0
  310. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-BR5s6Pws.js +106 -0
  311. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  312. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  313. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  314. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  315. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  316. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  317. ksadk/server/static/assets/index-BH7-pC0B.js +165 -0
  318. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  319. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +1 -0
  320. ksadk/server/static/assets/infoDiagram-5YYISTIA-DYPc5cvh.js +2 -0
  321. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  322. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-MoBdALDg.js +70 -0
  323. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  324. ksadk/server/static/assets/journeyDiagram-JHISSGLW-DgLiQT8l.js +139 -0
  325. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  326. ksadk/server/static/assets/kanban-definition-UN3LZRKU-CsFx112F.js +89 -0
  327. ksadk/server/static/assets/katex-DRsiwpM2.js +265 -0
  328. ksadk/server/static/assets/linear-TNRQ5Orv.js +1 -0
  329. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  330. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  331. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  332. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  333. ksadk/server/static/assets/mermaid-parser.core-D2eMVOgo.js +161 -0
  334. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-BtByYFhL.js +96 -0
  335. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  336. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  337. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  338. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  339. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  340. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  341. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  342. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  343. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  344. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  345. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  346. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +1 -0
  347. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  348. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  349. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +1 -0
  350. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPnuCB8c.js +30 -0
  351. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  352. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  353. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  354. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  355. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  356. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  357. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  358. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  359. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-miFpCPv4.js +7 -0
  360. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  361. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +1 -0
  362. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-mjB_yYkW.js +84 -0
  363. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  364. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  365. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-D7hSlZZZ.js +40 -0
  366. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  367. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  368. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-D9Cx6hC3.js +162 -0
  369. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  370. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  371. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  372. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  373. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  374. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  375. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  376. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  377. ksadk/server/static/assets/stateDiagram-AJRCARHV-D_zrg47o.js +1 -0
  378. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +1 -0
  379. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  380. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  381. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  382. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  383. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  384. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  385. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  386. ksadk/server/static/assets/timeline-definition-PNZ67QCA-CwSec0Wa.js +120 -0
  387. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  388. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +1 -0
  389. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +1 -0
  390. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  391. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  392. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  393. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  394. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  395. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  396. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  397. ksadk/server/static/assets/vennDiagram-CIIHVFJN-agCVobiF.js +34 -0
  398. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  399. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  400. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +1 -0
  401. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-TmrlhWNt.js +78 -0
  402. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  403. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  404. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  405. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-DV71V-ah.js +7 -0
  406. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  407. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  408. ksadk/server/static/favicon.svg +1 -0
  409. ksadk/server/static/icons.svg +24 -0
  410. ksadk/server/static/index.html +14 -0
  411. ksadk/server/terminal_sessions.py +551 -0
  412. ksadk/sessions/__init__.py +283 -0
  413. ksadk/sessions/base.py +354 -0
  414. ksadk/sessions/continuity.py +313 -0
  415. ksadk/sessions/errors.py +7 -0
  416. ksadk/sessions/in_memory.py +273 -0
  417. ksadk/sessions/local_service.py +949 -0
  418. ksadk/sessions/postgres_service.py +838 -0
  419. ksadk/sessions/sqlite_service.py +13 -0
  420. ksadk/skills/__init__.py +9 -0
  421. ksadk/skills/loader.py +46 -0
  422. ksadk/skills/models.py +120 -0
  423. ksadk/skills/package_store.py +104 -0
  424. ksadk/skills/runtime/__init__.py +17 -0
  425. ksadk/skills/runtime/agent.py +183 -0
  426. ksadk/skills/runtime/artifacts.py +39 -0
  427. ksadk/skills/runtime/backends/__init__.py +1 -0
  428. ksadk/skills/runtime/backends/disabled.py +8 -0
  429. ksadk/skills/runtime/backends/e2b.py +168 -0
  430. ksadk/skills/runtime/backends/local.py +95 -0
  431. ksadk/skills/runtime/base.py +103 -0
  432. ksadk/skills/runtime/executor.py +219 -0
  433. ksadk/skills/runtime/factory.py +37 -0
  434. ksadk/skills/runtime/loader.py +131 -0
  435. ksadk/skills/runtime/registry.py +150 -0
  436. ksadk/skills/runtime/request.py +67 -0
  437. ksadk/skills/service_client.py +239 -0
  438. ksadk/skills/service_env.py +58 -0
  439. ksadk/skills/tool_defs.py +221 -0
  440. ksadk/terminal_client.py +69 -0
  441. ksadk/terminal_exec_policy.py +229 -0
  442. ksadk/tools/__init__.py +17 -0
  443. ksadk/tools/gateway.py +230 -0
  444. ksadk/tools/result_budget.py +154 -0
  445. ksadk/toolsets/__init__.py +852 -0
  446. ksadk/toolsets/_langchain.py +11 -0
  447. ksadk/toolsets/platform.py +93 -0
  448. ksadk/toolsets/sandbox.py +310 -0
  449. ksadk/toolsets/skills.py +217 -0
  450. ksadk/toolsets/web.py +266 -0
  451. ksadk/toolsets/workspace.py +878 -0
  452. ksadk/toolsets/workspace_state.py +44 -0
  453. ksadk/tracing/__init__.py +37 -0
  454. ksadk/tracing/exporters/__init__.py +1 -0
  455. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  456. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  457. ksadk/tracing/setup.py +829 -0
  458. ksadk/tracing/span_utils.py +114 -0
  459. ksadk/tui/__init__.py +12 -0
  460. ksadk/tui/app.py +400 -0
  461. ksadk/tui/clipboard.py +100 -0
  462. ksadk/tui/widgets/__init__.py +22 -0
  463. ksadk/tui/widgets/assistant.py +89 -0
  464. ksadk/tui/widgets/base.py +54 -0
  465. ksadk/tui/widgets/chat_input.py +246 -0
  466. ksadk/tui/widgets/history.py +156 -0
  467. ksadk/tui/widgets/system.py +87 -0
  468. ksadk/tui/widgets/thinking.py +110 -0
  469. ksadk/tui/widgets/tool_call.py +161 -0
  470. ksadk/tui/widgets/user.py +37 -0
  471. ksadk/ui_config.py +168 -0
  472. ksadk/version.py +4 -0
  473. ksadk_runtime_common/__init__.py +20 -0
  474. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  475. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  476. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  477. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  478. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  479. ksadk_runtime_common/memory_backend/registry.py +56 -0
  480. ksadk_runtime_common/memory_backend/render.py +71 -0
  481. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  482. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  483. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  484. ksadk_runtime_common/workspace_files/constants.py +9 -0
  485. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  486. ksadk_runtime_common/workspace_files/preview.py +96 -0
  487. ksadk_runtime_common/workspace_files/router.py +254 -0
  488. agentengine_sdk_python-0.6.2.dist-info/METADATA +0 -46
  489. agentengine_sdk_python-0.6.2.dist-info/RECORD +0 -4
  490. agentengine_sdk_python-0.6.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,1135 @@
1
+ """
2
+ ADKRunner - Google ADK 框架运行时
3
+
4
+ 参考 adk-python 原生实现,缓存 Runner 和 SessionService。
5
+ 支持通过环境变量配置记忆体 (ShortTermMemory / LongTermMemory)。
6
+ """
7
+
8
+ import base64
9
+ import inspect
10
+ import logging
11
+ import os
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Any, AsyncIterator, Dict, Mapping, Optional
15
+
16
+ from opentelemetry import trace
17
+
18
+ from ksadk.conversations.attachments import classify_attachment_kind, read_attachment_uri_bytes
19
+ from ksadk.conversations.model_context import supports_native_image_input
20
+ from ksadk.runners.base_runner import BaseRunner
21
+ from ksadk.runners.usage_accumulator import accumulate_usage
22
+ from ksadk.sessions.continuity import ADKSessionAdapter
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ tracer = trace.get_tracer(__name__)
27
+
28
+
29
+ class ADKRunner(BaseRunner):
30
+ """ADK 框架运行时"""
31
+
32
+ def __init__(self, detection_result: Any, project_dir: str):
33
+ super().__init__(detection_result, project_dir)
34
+ self._runner = None
35
+ self._session_service = None
36
+ # Map external session_ids (e.g. from run_interactive or web) to ADK internal session IDs
37
+ self._session_map: Dict[str, str] = {}
38
+ # Fallback default session
39
+ self._default_session_id: Optional[str] = None
40
+ # Memory integration
41
+ self._short_term_memory = None
42
+ self._long_term_memory = None
43
+ # Knowledge base integration
44
+ self._knowledge_base = None
45
+ # Keep runtime toolsets alive for the lifetime of the runner.
46
+ self._runtime_toolsets: list[Any] = []
47
+
48
+ async def close(self) -> None:
49
+ """Close runtime toolsets owned by this runner."""
50
+ toolsets = list(self._runtime_toolsets)
51
+ self._runtime_toolsets.clear()
52
+ for toolset in toolsets:
53
+ close = getattr(toolset, "aclose", None) or getattr(toolset, "close", None)
54
+ if not callable(close):
55
+ continue
56
+ try:
57
+ result = close()
58
+ if inspect.isawaitable(result):
59
+ await result
60
+ except Exception as exc:
61
+ logger.warning("Failed to close runtime toolset %r: %s", toolset, exc)
62
+
63
+ def _apply_json_patch(self):
64
+ """Monkey patch google.adk.models.lite_llm to handle invalid JSON safely"""
65
+ try:
66
+ import json
67
+
68
+ import google.adk.models.lite_llm as adk_lite_llm
69
+
70
+ # Create a proxy for the json module
71
+ class RobustJson:
72
+ def __getattr__(self, name):
73
+ return getattr(json, name)
74
+
75
+ def loads(self, s, **kwargs):
76
+ result = {}
77
+ try:
78
+ result = json.loads(s, **kwargs)
79
+ except json.JSONDecodeError:
80
+ # Try json_repair if available
81
+ try:
82
+ import json_repair
83
+
84
+ result = json_repair.loads(s)
85
+ except ImportError:
86
+ # Fallback: return empty dict to prevent crash
87
+ print(
88
+ f"\n⚠️ [KSADK] Warning: Captured invalid JSON from LLM: {s[:50]}..."
89
+ )
90
+ result = {}
91
+
92
+ # Ensure result is a dict (Google GenAI FunctionCall requires dict args)
93
+ if not isinstance(result, dict):
94
+ return {}
95
+ return result
96
+
97
+ # Replace the 'json' module reference INSIDE lite_llm module
98
+ # This is safer than patching json.loads globally
99
+ adk_lite_llm.json = RobustJson()
100
+
101
+ except ImportError:
102
+ pass # ADK not installed
103
+ except Exception:
104
+ pass
105
+
106
+ def _init_short_term_memory(self):
107
+ """从环境变量初始化短期记忆
108
+
109
+ 环境变量:
110
+ KSADK_ADK_SESSION_BACKEND / PATH / URL: ADK 专用 session 配置
111
+ KSADK_STM_BACKEND / PATH / URL: 旧平台级 STM 配置
112
+ KSADK_SESSION_BACKEND / DSN: 统一 session 配置 fallback
113
+ """
114
+ configured_names = (
115
+ "KSADK_ADK_SESSION_BACKEND",
116
+ "KSADK_ADK_SESSION_PATH",
117
+ "KSADK_ADK_SESSION_URL",
118
+ "KSADK_STM_BACKEND",
119
+ "KSADK_STM_PATH",
120
+ "KSADK_STM_URL",
121
+ "KSADK_STM_DB_PATH",
122
+ "KSADK_STM_DB_URL",
123
+ "KSADK_SESSION_BACKEND",
124
+ "KSADK_SESSION_DSN",
125
+ )
126
+ if not any(str(os.environ.get(name, "")).strip() for name in configured_names):
127
+ return None
128
+
129
+ try:
130
+ from ksadk.memory.adk import ShortTermMemory
131
+
132
+ stm = ShortTermMemory.from_env()
133
+ logger.info(
134
+ "ShortTermMemory initialized: backend=%s path=%s",
135
+ stm.backend,
136
+ stm.local_database_path,
137
+ )
138
+ return stm
139
+ except Exception as e:
140
+ logger.warning(f"Failed to init ShortTermMemory: {e}. Using default.")
141
+ return None
142
+
143
+ def get_session_adapter(self):
144
+ return ADKSessionAdapter()
145
+
146
+ def describe_checkpoint_capability(self) -> dict[str, Any]:
147
+ return {
148
+ "Supported": False,
149
+ "Backend": "none",
150
+ "Scope": "unknown",
151
+ "Durable": False,
152
+ "SharedAcrossPods": False,
153
+ "ResumeMode": "forward_only",
154
+ "Reason": "ADK native session can continue conversation context, but KSADK does not expose ADK framework checkpoint restore points",
155
+ }
156
+
157
+ def get_runtime_capabilities(self) -> dict[str, Any]:
158
+ capabilities = super().get_runtime_capabilities()
159
+ has_native_session = bool(getattr(self, "_short_term_memory", None)) or any(
160
+ str(os.getenv(name) or "").strip()
161
+ for name in (
162
+ "KSADK_ADK_SESSION_BACKEND",
163
+ "KSADK_ADK_SESSION_PATH",
164
+ "KSADK_ADK_SESSION_URL",
165
+ "KSADK_STM_BACKEND",
166
+ "KSADK_STM_PATH",
167
+ "KSADK_STM_URL",
168
+ "KSADK_STM_DB_PATH",
169
+ "KSADK_STM_DB_URL",
170
+ "KSADK_SESSION_BACKEND",
171
+ "KSADK_SESSION_DSN",
172
+ )
173
+ )
174
+ capabilities["SessionContinuity"] = {
175
+ "Supported": True,
176
+ "Type": "native_session" if has_native_session else "semantic_replay",
177
+ "Level": "semantic",
178
+ "Reason": "ADK native session can continue conversation context"
179
+ if has_native_session
180
+ else "conversation transcript can be replayed",
181
+ }
182
+ capabilities["ResumeRun"]["Reason"] = capabilities["Checkpoint"]["Reason"]
183
+ return capabilities
184
+
185
+ def _init_long_term_memory(self):
186
+ """从环境变量初始化长期记忆
187
+
188
+ 环境变量:
189
+ KSADK_LTM_BACKEND: local / http / sdk
190
+ KSADK_LTM_HTTP_URL: HTTP 记忆服务地址
191
+ KSADK_LTM_HTTP_TOKEN: 认证 Token
192
+ KSADK_LTM_ACCESS_KEY: SDK AK (fallback to KSYUN_ACCESS_KEY)
193
+ KSADK_LTM_SECRET_KEY: SDK SK (fallback to KSYUN_SECRET_KEY)
194
+ KSADK_LTM_TOP_K: 检索数量
195
+ """
196
+ backend = os.environ.get("KSADK_LTM_BACKEND", "")
197
+ if not backend:
198
+ return None
199
+
200
+ try:
201
+ from ksadk.memory.adk import LongTermMemory
202
+
203
+ agent_name = self._agent.name if self._agent else "default"
204
+ ltm = LongTermMemory.from_env(app_name=agent_name)
205
+ logger.info(
206
+ f"LongTermMemory initialized: backend={backend}, "
207
+ f"app_name={agent_name}"
208
+ )
209
+ return ltm
210
+ except Exception as e:
211
+ logger.warning(f"Failed to init LongTermMemory: {e}.")
212
+ return None
213
+
214
+ def _init_knowledge_base(self):
215
+ """从环境变量初始化知识库
216
+
217
+ 环境变量:
218
+ KSADK_KB_DATASET_ID: 知识库 ID (必填,存在即启用)
219
+ KSADK_KB_ACCESS_KEY: AK (可选)
220
+ KSADK_KB_SECRET_KEY: SK (可选)
221
+ KSADK_KB_REGION: 区域 (默认 cn-beijing-6)
222
+ KSADK_KB_TOP_K: 返回结果数 (默认 5)
223
+ """
224
+ try:
225
+ from ksadk.knowledge_base.client import KnowledgeBaseClient
226
+
227
+ if not KnowledgeBaseClient.is_configured():
228
+ return None
229
+
230
+ kb = KnowledgeBaseClient.from_env()
231
+ logger.info(
232
+ f"KnowledgeBase initialized: dataset_id={kb.dataset_id}, "
233
+ f"region={kb.region}"
234
+ )
235
+ return kb
236
+ except ImportError:
237
+ logger.warning(
238
+ "kingsoftcloud-sdk-python not installed, "
239
+ "knowledge base disabled. "
240
+ "Install with: pip install kingsoftcloud-sdk-python"
241
+ )
242
+ return None
243
+ except Exception as e:
244
+ logger.warning(f"Failed to init KnowledgeBase: {e}.")
245
+ return None
246
+
247
+ def _inject_search_knowledge_tool(self):
248
+ """自动注入 search_knowledge_base 工具到 Agent"""
249
+ try:
250
+ from ksadk.knowledge_base.adk_tool import search_knowledge_base
251
+
252
+ added = self._append_tools_by_name([search_knowledge_base])
253
+ if added:
254
+ logger.info(
255
+ "Injected 'search_knowledge_base' tool into agent "
256
+ f"(total tools: {len(self._agent.tools)})"
257
+ )
258
+ else:
259
+ logger.debug("Agent already has 'search_knowledge_base' tool")
260
+ except ImportError as e:
261
+ logger.warning(f"Failed to import knowledge base tool: {e}")
262
+ except Exception as e:
263
+ logger.warning(f"Failed to inject search_knowledge_base tool: {e}")
264
+
265
+ def _inject_load_memory_tool(self):
266
+ """自动注入 load_memory 工具到 Agent"""
267
+ try:
268
+ from google.adk.tools import load_memory
269
+
270
+ added = self._append_tools_by_name([load_memory])
271
+ if added:
272
+ logger.info(
273
+ "Injected 'load_memory' tool into agent "
274
+ f"(total tools: {len(self._agent.tools)})"
275
+ )
276
+ else:
277
+ logger.debug("Agent already has 'load_memory' tool")
278
+ except ImportError:
279
+ logger.warning(
280
+ "google.adk.tools.load_memory not available. "
281
+ "Ensure google-adk >= 1.0.0 is installed."
282
+ )
283
+ except Exception as e:
284
+ logger.warning(f"Failed to inject load_memory tool: {e}")
285
+
286
+ def _inject_save_memory_tool(self):
287
+ """自动注入 save_memory 工具到 Agent"""
288
+ try:
289
+ from ksadk.memory.adk_tool import create_adk_tool
290
+
291
+ save_memory_tool = create_adk_tool()
292
+ added = self._append_tools_by_name([save_memory_tool])
293
+ if added:
294
+ logger.info(
295
+ "Injected 'save_memory' tool into agent "
296
+ f"(total tools: {len(self._agent.tools)})"
297
+ )
298
+ else:
299
+ logger.debug("Agent already has 'save_memory' tool")
300
+ except ImportError as e:
301
+ logger.warning(f"Failed to import save_memory tool: {e}")
302
+ except Exception as e:
303
+ logger.warning(f"Failed to inject save_memory tool: {e}")
304
+
305
+ def _inject_skill_runtime_tools(self):
306
+ """Inject Skill Runtime tools when skills are configured for sandbox mode."""
307
+ mode = self._resolve_skills_mode()
308
+ if mode == "local":
309
+ self._inject_local_skill_tools()
310
+ return
311
+ if mode != "sandbox":
312
+ return
313
+
314
+ try:
315
+ from ksadk.skills.runtime import create_skill_runtime_backend
316
+ from ksadk.skills.tool_defs import (
317
+ build_execute_skills_tool,
318
+ build_skill_manifest_instruction,
319
+ load_remote_skill_manifests,
320
+ resolve_skill_space_ids,
321
+ resolve_user_skill_space_ids,
322
+ )
323
+
324
+ skill_space_ids = resolve_skill_space_ids()
325
+ backend = create_skill_runtime_backend()
326
+ execute_skills = build_execute_skills_tool(
327
+ backend=backend,
328
+ skill_space_ids=resolve_user_skill_space_ids(),
329
+ session_id=getattr(self._agent, "name", None) or self.detection_result.name,
330
+ )
331
+ added = self._append_tools_by_name([execute_skills])
332
+ try:
333
+ manifest_instruction = build_skill_manifest_instruction(
334
+ load_remote_skill_manifests(skill_space_ids)
335
+ )
336
+ if manifest_instruction:
337
+ self._append_agent_instruction(manifest_instruction)
338
+ except Exception as exc:
339
+ logger.warning("Failed to inject remote Skill manifest: %s", exc)
340
+ if added:
341
+ logger.info("Injected Skill Runtime tools into agent (added: %s)", ", ".join(added))
342
+ else:
343
+ logger.debug("Skill Runtime tools already present")
344
+ except Exception as exc:
345
+ logger.warning("Failed to inject Skill Runtime tools: %s", exc)
346
+
347
+ def _append_agent_instruction(self, extra_instruction: str) -> None:
348
+ if not extra_instruction or not hasattr(self._agent, "instruction"):
349
+ return
350
+ current = str(getattr(self._agent, "instruction") or "")
351
+ if extra_instruction in current:
352
+ return
353
+ self._agent.instruction = f"{current.rstrip()}\n{extra_instruction}".strip()
354
+
355
+ def _inject_local_skill_tools(self):
356
+ try:
357
+ from ksadk.skills.loader import load_local_skill
358
+ from ksadk.skills.tool_defs import build_skills_tool
359
+
360
+ skills_dir = Path(
361
+ os.environ.get("KSADK_LOCAL_SKILLS_DIR")
362
+ or os.environ.get("KSADK_SKILL_CACHE_DIR")
363
+ or Path(self.project_dir) / "skills"
364
+ )
365
+ if not skills_dir.exists():
366
+ logger.info("ADKRunner: local skills directory does not exist: %s", skills_dir)
367
+ return
368
+ skills = [
369
+ load_local_skill(path)
370
+ for path in sorted(skills_dir.iterdir())
371
+ if path.is_dir() and (path / "SKILL.md").exists()
372
+ ]
373
+ if not skills:
374
+ return
375
+ tool = build_skills_tool(skills)
376
+ added = self._append_tools_by_name([tool])
377
+ if added:
378
+ logger.info("Injected local Skill tools into agent (added: %s)", ", ".join(added))
379
+ except Exception as exc:
380
+ logger.warning("Failed to inject local Skill tools: %s", exc)
381
+
382
+ def _resolve_skills_mode(self) -> str:
383
+ mode = os.environ.get("KSADK_SKILLS_MODE", "auto").strip().lower()
384
+ if mode != "auto":
385
+ return mode
386
+ runtime_backend = os.environ.get("KSADK_SKILL_RUNTIME_BACKEND")
387
+ if runtime_backend is not None:
388
+ backend = runtime_backend.strip().lower()
389
+ return "sandbox" if backend and backend not in {"disabled", "none", "off"} else "auto"
390
+
391
+ backend = (os.environ.get("KSADK_SANDBOX_BACKEND") or "").strip().lower()
392
+ if backend and backend not in {"disabled", "none", "off"}:
393
+ return "sandbox"
394
+ if os.environ.get("KSADK_SANDBOX_TEMPLATE_ID") or os.environ.get("KSADK_SKILL_RUNTIME_TEMPLATE_ID"):
395
+ return "sandbox"
396
+ skills_dir = Path(
397
+ os.environ.get("KSADK_LOCAL_SKILLS_DIR")
398
+ or os.environ.get("KSADK_SKILL_CACHE_DIR")
399
+ or Path(self.project_dir) / "skills"
400
+ )
401
+ if self._has_local_skills(skills_dir):
402
+ return "local"
403
+ return "auto"
404
+
405
+ @staticmethod
406
+ def _has_local_skills(skills_dir: Path) -> bool:
407
+ if not skills_dir.exists():
408
+ return False
409
+ return any(path.is_dir() and (path / "SKILL.md").exists() for path in skills_dir.iterdir())
410
+
411
+ def _inject_mcp_toolsets(self):
412
+ """默认注入远端 MCP toolset。"""
413
+ try:
414
+ from ksadk.mcp_runtime import (
415
+ MCP_TOOLSET_KEY_ATTR,
416
+ load_mcp_toolsets_from_env,
417
+ mcp_tools_enabled,
418
+ register_mcp_toolset_descriptors,
419
+ )
420
+
421
+ if not mcp_tools_enabled():
422
+ logger.info("ADKRunner: MCP tools disabled via KSADK_ENABLE_MCP_TOOLS=0")
423
+ return
424
+
425
+ toolsets = load_mcp_toolsets_from_env()
426
+ if not toolsets:
427
+ return
428
+
429
+ added = self._append_toolsets_by_key(
430
+ toolsets,
431
+ key_attr=MCP_TOOLSET_KEY_ATTR,
432
+ )
433
+ if not added:
434
+ logger.debug("ADKRunner: MCP toolsets already present")
435
+ return
436
+
437
+ for toolset in toolsets:
438
+ key = getattr(toolset, MCP_TOOLSET_KEY_ATTR, None)
439
+ if key in added:
440
+ self._runtime_toolsets.append(toolset)
441
+ register_mcp_toolset_descriptors(toolset)
442
+ logger.info("Injected MCP toolsets into agent (added: %s)", ", ".join(added))
443
+ except ImportError as exc:
444
+ logger.warning(f"Failed to import MCP runtime helpers: {exc}")
445
+ except Exception as exc:
446
+ logger.warning(f"Failed to inject MCP toolsets: {exc}")
447
+
448
+ def _inject_builtin_tools(self):
449
+ """Inject ksadk built-in tools according to the runtime profile."""
450
+ try:
451
+ from ksadk.toolsets import builtin_tools_for_runtime, builtin_tools_mode
452
+
453
+ mode = builtin_tools_mode(default="off")
454
+ if mode == "off":
455
+ return
456
+ tools = builtin_tools_for_runtime(mode=mode)
457
+ if not tools:
458
+ return
459
+ added = self._append_tools_by_name(tools)
460
+ if added:
461
+ logger.info("Injected ksadk built-in tools into agent (added: %s)", ", ".join(added))
462
+ else:
463
+ logger.debug("ksadk built-in tools already present")
464
+ except Exception as exc:
465
+ logger.warning("Failed to inject ksadk built-in tools: %s", exc)
466
+
467
+ def inject_deferred_tools_for_request(self, tool_names: list[str] | tuple[str, ...]) -> list[str]:
468
+ """Append direct built-in tools selected by deferred tool search."""
469
+ names = [str(name or "").strip() for name in tool_names or [] if str(name or "").strip()]
470
+ if not names:
471
+ return []
472
+ try:
473
+ from ksadk.toolsets import get_agentengine_tools
474
+
475
+ tools = get_agentengine_tools(include=names, profile="coding", mode="direct")
476
+ added = self._append_tools_by_name(tools)
477
+ if added:
478
+ logger.info("Injected deferred ksadk tools for request (added: %s)", ", ".join(added))
479
+ return added
480
+ except Exception as exc:
481
+ logger.warning("Failed to inject deferred ksadk tools for request: %s", exc)
482
+ return []
483
+
484
+ @staticmethod
485
+ def _tool_name(tool: Any) -> str:
486
+ return getattr(tool, "name", None) or getattr(tool, "__name__", "")
487
+
488
+ def _append_tools_by_name(self, tools: list[Any]) -> list[str]:
489
+ if not hasattr(self._agent, "tools"):
490
+ logger.warning("Agent has no 'tools' attribute, cannot inject runtime tools")
491
+ return []
492
+
493
+ if self._agent.tools is None:
494
+ self._agent.tools = []
495
+
496
+ existing_names = {
497
+ self._tool_name(tool)
498
+ for tool in self._agent.tools
499
+ if self._tool_name(tool)
500
+ }
501
+ added_names: list[str] = []
502
+ for tool in tools:
503
+ tool_name = self._tool_name(tool)
504
+ if tool_name and tool_name in existing_names:
505
+ continue
506
+ self._agent.tools.append(tool)
507
+ if tool_name:
508
+ existing_names.add(tool_name)
509
+ added_names.append(tool_name)
510
+ return added_names
511
+
512
+ def _append_toolsets_by_key(self, toolsets: list[Any], *, key_attr: str) -> list[str]:
513
+ if not hasattr(self._agent, "tools"):
514
+ logger.warning("Agent has no 'tools' attribute, cannot inject MCP toolsets")
515
+ return []
516
+
517
+ if self._agent.tools is None:
518
+ self._agent.tools = []
519
+
520
+ existing_keys = {
521
+ getattr(tool, key_attr)
522
+ for tool in self._agent.tools
523
+ if getattr(tool, key_attr, None)
524
+ }
525
+ added_keys: list[str] = []
526
+ for toolset in toolsets:
527
+ key = getattr(toolset, key_attr, None)
528
+ if key and key in existing_keys:
529
+ continue
530
+ self._agent.tools.append(toolset)
531
+ if key:
532
+ existing_keys.add(key)
533
+ added_keys.append(key)
534
+ return added_keys
535
+
536
+ def load_agent(self) -> None:
537
+ """加载 ADK Agent"""
538
+ import warnings
539
+
540
+ warnings.filterwarnings("ignore", category=UserWarning, module="pydantic.main")
541
+
542
+ self._apply_json_patch()
543
+
544
+ # 添加项目目录到 Python 路径
545
+ project_path = Path(self.project_dir).resolve()
546
+ if str(project_path) not in sys.path:
547
+ sys.path.insert(0, str(project_path))
548
+
549
+ # 确定模块名: 从 entry_point 获取
550
+ # (e.g. "smart_assistant_adk/agent.py" -> "smart_assistant_adk.agent")
551
+ entry_point = self.detection_result.entry_point
552
+ if entry_point.endswith(".py"):
553
+ module_name = entry_point[:-3] # 移除 .py 后缀
554
+ else:
555
+ module_name = entry_point
556
+
557
+ # 转换路径为模块路径 (e.g., "subdir/agent" -> "subdir.agent")
558
+ module_name = module_name.replace("/", ".").replace("\\", ".")
559
+
560
+ try:
561
+ module = __import__(module_name, fromlist=[self.detection_result.agent_variable])
562
+ self._agent = getattr(module, self.detection_result.agent_variable)
563
+
564
+ # Inject safety instruction for DeepSeek/LLMs to prevent empty tool names
565
+ if hasattr(self._agent, "instruction"):
566
+ safety_prompt = "\nIMPORTANT: Do NOT output tool calls with empty names."
567
+ if self._agent.instruction:
568
+ self._agent.instruction += safety_prompt
569
+ else:
570
+ self._agent.instruction = safety_prompt
571
+
572
+ except ImportError as e:
573
+ raise ImportError(f"无法导入模块 {module_name}: {e}")
574
+ except AttributeError:
575
+ raise AttributeError(
576
+ f"模块 {module_name} 中未找到 {self.detection_result.agent_variable}"
577
+ )
578
+
579
+ # 验证是否为 ADK Agent
580
+ if not hasattr(self._agent, "name"):
581
+ raise TypeError("加载的对象不是有效的 ADK Agent")
582
+
583
+ # 初始化记忆体 (从环境变量读取配置)
584
+ self._short_term_memory = self._init_short_term_memory()
585
+ self._long_term_memory = self._init_long_term_memory()
586
+
587
+ # 初始化知识库 (从环境变量读取配置)
588
+ self._knowledge_base = self._init_knowledge_base()
589
+ if self._knowledge_base:
590
+ self._inject_search_knowledge_tool()
591
+
592
+ # 初始化 SessionService
593
+ from google.adk.runners import Runner
594
+ from google.adk.sessions import InMemorySessionService
595
+
596
+ if self._short_term_memory:
597
+ self._session_service = self._short_term_memory.session_service
598
+ logger.info("ADKRunner: using ShortTermMemory session service")
599
+ else:
600
+ self._session_service = InMemorySessionService()
601
+
602
+ # 如果配置了长期记忆,自动注入 load_memory 工具到 agent
603
+ if self._long_term_memory:
604
+ self._inject_load_memory_tool()
605
+ self._inject_save_memory_tool()
606
+
607
+ self._inject_skill_runtime_tools()
608
+ self._inject_builtin_tools()
609
+ self._inject_mcp_toolsets()
610
+
611
+ # 初始化 Runner (传入 memory_service)
612
+ runner_kwargs = dict(
613
+ agent=self._agent,
614
+ session_service=self._session_service,
615
+ app_name=self._agent.name,
616
+ )
617
+ if self._long_term_memory:
618
+ runner_kwargs["memory_service"] = self._long_term_memory
619
+ logger.info("ADKRunner: LongTermMemory injected as memory_service")
620
+
621
+ self._runner = Runner(**runner_kwargs)
622
+ self._default_model_name = self.normalize_requested_model(
623
+ os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
624
+ )
625
+ self._default_model_reference = self._discover_model_reference(self._agent)
626
+ self._active_model_name = (
627
+ self._default_model_reference
628
+ or self._default_model_name
629
+ )
630
+
631
+ def _discover_model_reference(self, agent: Any) -> Optional[str]:
632
+ visited: set[int] = set()
633
+
634
+ def _visit(node: Any) -> Optional[str]:
635
+ if node is None:
636
+ return None
637
+ node_id = id(node)
638
+ if node_id in visited:
639
+ return None
640
+ visited.add(node_id)
641
+
642
+ current_model = getattr(node, "model", None)
643
+ if hasattr(current_model, "model"):
644
+ candidate = str(getattr(current_model, "model", None) or "").strip()
645
+ if candidate:
646
+ return candidate
647
+ elif isinstance(current_model, str):
648
+ candidate = current_model.strip()
649
+ if candidate:
650
+ return candidate
651
+
652
+ for child in getattr(node, "sub_agents", []) or []:
653
+ discovered = _visit(child)
654
+ if discovered:
655
+ return discovered
656
+ return None
657
+
658
+ return _visit(agent)
659
+
660
+ @staticmethod
661
+ def _resolve_model_reference(existing_model: Any, requested_model: str) -> str:
662
+ existing = str(existing_model or "").strip()
663
+ requested = requested_model.strip()
664
+ if "/" in requested:
665
+ return requested
666
+ if "/" in existing:
667
+ provider_prefix = existing.split("/", 1)[0]
668
+ return f"{provider_prefix}/{requested}"
669
+ return requested
670
+
671
+ def _apply_model_to_agent_tree(self, agent: Any, requested_model: str) -> None:
672
+ visited: set[int] = set()
673
+
674
+ def _visit(node: Any) -> None:
675
+ if node is None:
676
+ return
677
+ node_id = id(node)
678
+ if node_id in visited:
679
+ return
680
+ visited.add(node_id)
681
+
682
+ current_model = getattr(node, "model", None)
683
+ if hasattr(current_model, "model"):
684
+ current_reference = getattr(current_model, "model", None)
685
+ next_reference = self._resolve_model_reference(current_reference, requested_model)
686
+ if current_reference != next_reference:
687
+ setattr(current_model, "model", next_reference)
688
+ elif isinstance(current_model, str):
689
+ next_reference = self._resolve_model_reference(current_model, requested_model)
690
+ if current_model != next_reference:
691
+ setattr(node, "model", next_reference)
692
+
693
+ for child in getattr(node, "sub_agents", []) or []:
694
+ _visit(child)
695
+
696
+ _visit(agent)
697
+
698
+ def prepare_for_request(self, model: str | None) -> None:
699
+ normalized = self.sync_process_model_env(model)
700
+ if normalized is None:
701
+ default_model_name = (
702
+ getattr(self, "_default_model_name", None)
703
+ or self.normalize_requested_model(
704
+ os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
705
+ )
706
+ )
707
+ if default_model_name:
708
+ self.sync_process_model_env(default_model_name)
709
+ target_reference = (
710
+ getattr(self, "_default_model_reference", None)
711
+ or default_model_name
712
+ )
713
+ if target_reference and self._agent is not None:
714
+ current_reference = self._discover_model_reference(self._agent)
715
+ if current_reference != target_reference:
716
+ self._apply_model_to_agent_tree(self._agent, target_reference)
717
+ self._active_model_name = target_reference
718
+ return
719
+ target_reference = (
720
+ self._resolve_model_reference(
721
+ self._discover_model_reference(self._agent)
722
+ or getattr(self, "_default_model_reference", None)
723
+ or normalized,
724
+ normalized,
725
+ )
726
+ if self._agent is not None
727
+ else normalized
728
+ )
729
+ if target_reference == getattr(self, "_active_model_name", None):
730
+ return
731
+ if self._agent is not None:
732
+ self._apply_model_to_agent_tree(self._agent, normalized)
733
+ self._active_model_name = self._discover_model_reference(self._agent) or target_reference
734
+ return
735
+ self._active_model_name = target_reference
736
+
737
+ def _prepare_trace_metadata(self, session_id: str):
738
+ """准备 Trace 元数据 (Tags, UserID, etc.)"""
739
+ from ksadk.tracing.span_utils import prepare_trace_metadata
740
+ return prepare_trace_metadata(
741
+ detection_result=getattr(self, "detection_result", None)
742
+ )
743
+
744
+ async def _ensure_session(self, external_session_id: str = None) -> str:
745
+ """Get or create ADK session ID based on external ID
746
+
747
+ When ShortTermMemory is configured, uses its create_session method
748
+ which supports session retrieval (if session_id already exists).
749
+ """
750
+ # Case 1: External ID provided
751
+ if external_session_id:
752
+ if external_session_id in self._session_map:
753
+ return self._session_map[external_session_id]
754
+
755
+ # Create new ADK session and map it
756
+ if self._short_term_memory:
757
+ session = await self._short_term_memory.create_session(
758
+ app_name=self._agent.name,
759
+ user_id="ksadk_user",
760
+ session_id=external_session_id,
761
+ )
762
+ else:
763
+ session = await self._session_service.create_session(
764
+ app_name=self._agent.name, user_id="ksadk_user"
765
+ )
766
+ self._session_map[external_session_id] = session.id
767
+ return session.id
768
+
769
+ # Case 2: No external ID (use default singleton)
770
+ if self._default_session_id is None:
771
+ if self._short_term_memory:
772
+ session = await self._short_term_memory.create_session(
773
+ app_name=self._agent.name,
774
+ user_id="ksadk_user",
775
+ )
776
+ else:
777
+ session = await self._session_service.create_session(
778
+ app_name=self._agent.name, user_id="ksadk_user"
779
+ )
780
+ self._default_session_id = session.id
781
+ return self._default_session_id
782
+
783
+ async def save_session_to_long_term_memory(
784
+ self, session_id: str, user_id: str = "ksadk_user"
785
+ ) -> bool:
786
+ """将指定 session 保存到长期记忆
787
+
788
+ Args:
789
+ session_id: ADK 内部 session ID
790
+ user_id: 用户 ID
791
+
792
+ Returns:
793
+ 是否保存成功
794
+ """
795
+ if not self._long_term_memory:
796
+ logger.warning("LongTermMemory not configured, cannot save session.")
797
+ return False
798
+
799
+ try:
800
+ session = await self._session_service.get_session(
801
+ app_name=self._agent.name,
802
+ user_id=user_id,
803
+ session_id=session_id,
804
+ )
805
+ if not session:
806
+ logger.error(f"Session {session_id} not found, cannot save.")
807
+ return False
808
+
809
+ await self._long_term_memory.add_session_to_memory(session)
810
+ logger.info(f"Session {session_id} saved to long-term memory.")
811
+ return True
812
+ except Exception as e:
813
+ logger.error(f"Error saving session to long-term memory: {e}")
814
+ return False
815
+
816
+ def _build_adk_content(
817
+ self,
818
+ text: str,
819
+ attachments: list[Dict[str, Any]],
820
+ *,
821
+ model_metadata: Dict[str, Any] | None = None,
822
+ ) -> "types.Content":
823
+ from google.genai import types
824
+ parts = []
825
+ if text:
826
+ parts.append(types.Part(text=text))
827
+ skipped_images: list[str] = []
828
+ image_input_supported = supports_native_image_input(model_metadata)
829
+ for att in attachments:
830
+ mime_type = att.get("mime_type", "application/octet-stream")
831
+ display_name = att.get("display_name", "")
832
+ if classify_attachment_kind(str(mime_type), str(display_name)) == "image" and not image_input_supported:
833
+ skipped_images.append(str(display_name or "未命名图片"))
834
+ continue
835
+
836
+ data: Optional[bytes] = None
837
+
838
+ inline_data = att.get("data")
839
+ if att.get("transport") == "inline" and inline_data:
840
+ try:
841
+ data = base64.b64decode(str(inline_data).strip() + "===")
842
+ except Exception as e:
843
+ logger.warning(f"Failed to decode inline attachment {att.get('display_name', 'uploaded_file')}: {e}")
844
+
845
+ if data is None:
846
+ file_uri = att.get("file_uri")
847
+ if file_uri:
848
+ data = read_attachment_uri_bytes(file_uri)
849
+ if data is None:
850
+ logger.warning("Failed to load stored attachment %s", file_uri)
851
+
852
+ if data is None:
853
+ file_uri = att.get("file_uri", "")
854
+ if file_uri.startswith("local:"):
855
+ logger.warning(
856
+ "Ignoring direct local attachment reference %s; only resolved storage paths are allowed.",
857
+ file_uri,
858
+ )
859
+
860
+ if data is not None:
861
+ parts.append(types.Part.from_bytes(data=data, mime_type=mime_type))
862
+
863
+ if skipped_images:
864
+ image_list = "、".join(skipped_images)
865
+ parts.append(
866
+ types.Part(
867
+ text=(
868
+ "系统提示:当前模型不支持图片输入,"
869
+ f"无法直接分析图片附件({image_list})。"
870
+ "请切换到支持视觉的模型后重试。"
871
+ )
872
+ )
873
+ )
874
+
875
+ # If no parts were found at all (e.g. empty message), fallback to prevent crash
876
+ if not parts:
877
+ parts.append(types.Part(text="[empty message]"))
878
+
879
+ return types.Content(role="user", parts=parts)
880
+
881
+ def _build_state_delta(self, input_data: Dict[str, Any]) -> dict[str, Any]:
882
+ state_delta: dict[str, Any] = {}
883
+ for key in (
884
+ "input_parts",
885
+ "attachments",
886
+ "attachment_results",
887
+ "current_attachments",
888
+ "current_attachment_results",
889
+ "has_current_files",
890
+ ):
891
+ if key in input_data:
892
+ state_delta[key] = input_data.get(key)
893
+ return state_delta
894
+
895
+ @staticmethod
896
+ def _normalize_usage_metadata(usage_metadata: Any) -> dict[str, Any]:
897
+ if usage_metadata is None:
898
+ return {}
899
+ if hasattr(usage_metadata, "model_dump"):
900
+ try:
901
+ usage_metadata = usage_metadata.model_dump(exclude_none=True)
902
+ except Exception:
903
+ usage_metadata = None
904
+ elif hasattr(usage_metadata, "dict"):
905
+ try:
906
+ usage_metadata = usage_metadata.dict()
907
+ except Exception:
908
+ usage_metadata = None
909
+ if not isinstance(usage_metadata, Mapping):
910
+ return {}
911
+
912
+ reasoning_tokens = usage_metadata.get("thoughts_token_count")
913
+ output_token_details = {}
914
+ if reasoning_tokens is not None:
915
+ try:
916
+ output_token_details["reasoning"] = int(reasoning_tokens)
917
+ except (TypeError, ValueError):
918
+ pass
919
+
920
+ input_token_details: dict[str, Any] = {}
921
+ cached_tokens = usage_metadata.get("cached_content_token_count")
922
+ if cached_tokens is not None:
923
+ try:
924
+ input_token_details["cached"] = int(cached_tokens)
925
+ except (TypeError, ValueError):
926
+ pass
927
+ tool_use_tokens = usage_metadata.get("tool_use_prompt_token_count")
928
+ if tool_use_tokens is not None:
929
+ try:
930
+ input_token_details["tool_use"] = int(tool_use_tokens)
931
+ except (TypeError, ValueError):
932
+ pass
933
+
934
+ if "input_tokens" in usage_metadata or "output_tokens" in usage_metadata:
935
+ input_tokens = int(usage_metadata.get("input_tokens") or 0)
936
+ output_tokens = int(usage_metadata.get("output_tokens") or 0)
937
+ total_tokens = int(usage_metadata.get("total_tokens") or (input_tokens + output_tokens))
938
+ direct_input_details = usage_metadata.get("input_token_details")
939
+ if isinstance(direct_input_details, Mapping):
940
+ input_token_details.update(dict(direct_input_details))
941
+ normalized = {
942
+ "input_tokens": input_tokens,
943
+ "output_tokens": output_tokens,
944
+ "total_tokens": total_tokens,
945
+ "input_token_details": input_token_details,
946
+ "output_token_details": output_token_details,
947
+ }
948
+ direct_output_details = usage_metadata.get("output_token_details")
949
+ if isinstance(direct_output_details, Mapping):
950
+ normalized["output_token_details"] = dict(direct_output_details)
951
+ return normalized
952
+
953
+ input_tokens = int(usage_metadata.get("prompt_token_count") or 0)
954
+ output_tokens = int(usage_metadata.get("candidates_token_count") or 0)
955
+ total_tokens = int(usage_metadata.get("total_token_count") or (input_tokens + output_tokens))
956
+ if not (input_tokens or output_tokens or total_tokens or input_token_details or output_token_details):
957
+ return {}
958
+ return {
959
+ "input_tokens": input_tokens,
960
+ "output_tokens": output_tokens,
961
+ "total_tokens": total_tokens,
962
+ "input_token_details": input_token_details,
963
+ "output_token_details": output_token_details,
964
+ }
965
+
966
+ @classmethod
967
+ def _extract_event_usage(cls, event: Any) -> dict[str, Any]:
968
+ return cls._normalize_usage_metadata(getattr(event, "usage_metadata", None))
969
+
970
+ async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
971
+ """调用 ADK Agent"""
972
+ from google.genai import types
973
+
974
+ user_input = input_data.get("input", "")
975
+ instructions = str(input_data.get("instructions") or "").strip()
976
+ if instructions:
977
+ user_input = f"{instructions}\n\nCurrent user input:\n{user_input or '[empty message]'}"
978
+
979
+ # 1. 准备 Metadata (提前以此获取 Agent Name)
980
+ _, _, _, agent_name = self._prepare_trace_metadata(None)
981
+ trace_name = agent_name or "adk.invoke"
982
+
983
+ with tracer.start_as_current_span(trace_name) as span:
984
+ # Set input.value for Langfuse top-level input display
985
+ span.set_attribute("input.value", user_input)
986
+ span.set_attribute("user.input", user_input[:200])
987
+
988
+ # Use external session ID if provided
989
+ req_session_id = input_data.get("session_id")
990
+ session_id = await self._ensure_session(req_session_id)
991
+
992
+ # 准备 Metadata 并设置 Span Attributes
993
+ # Langfuse Exporter 会读取这些 span attributes
994
+ agent_user_id, tags, _, _ = self._prepare_trace_metadata(session_id)
995
+
996
+ span.set_attribute("langfuse.session_id", session_id)
997
+ if agent_user_id:
998
+ span.set_attribute("langfuse.user_id", agent_user_id)
999
+ if tags:
1000
+ span.set_attribute("langfuse.tags", ",".join(tags))
1001
+
1002
+ new_message = self._build_adk_content(
1003
+ user_input,
1004
+ input_data.get("attachments", []),
1005
+ model_metadata=input_data.get("model_metadata"),
1006
+ )
1007
+ state_delta = self._build_state_delta(input_data)
1008
+
1009
+ final_response = ""
1010
+
1011
+ events_list = []
1012
+ usage: dict[str, Any] = {}
1013
+ last_usage: dict[str, Any] = {}
1014
+ async for event in self._runner.run_async(
1015
+ session_id=session_id,
1016
+ user_id="ksadk_user",
1017
+ new_message=new_message,
1018
+ state_delta=state_delta or None,
1019
+ ):
1020
+ events_list.append(event)
1021
+ event_usage = self._extract_event_usage(event)
1022
+ if event_usage:
1023
+ usage = accumulate_usage(usage, event_usage)
1024
+ last_usage = event_usage # 保留最后一个非空(窗口占用=最后一次 input)
1025
+ if hasattr(event, "content") and event.content:
1026
+ if hasattr(event.content, "parts"):
1027
+ for part in event.content.parts:
1028
+ # 过滤掉思考内容 (thought=True),只保留最终答案
1029
+ is_thought = getattr(part, "thought", False)
1030
+ if hasattr(part, "text") and part.text and not is_thought:
1031
+ final_response = part.text
1032
+
1033
+ # Set output.value for Langfuse top-level output display
1034
+ span.set_attribute("output.value", final_response[:5000] if final_response else "")
1035
+ span.set_attribute("agent.output", final_response[:500] if final_response else "")
1036
+ result = {"output": final_response, "events": events_list}
1037
+ if usage:
1038
+ result["usage"] = usage
1039
+ # last_usage = 最后一次 LLM 调用快照(input_tokens = 当前上下文窗口占用)
1040
+ result.setdefault("metadata", {})["last_usage"] = last_usage
1041
+ return result
1042
+
1043
+ async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
1044
+ """流式调用 ADK Agent
1045
+
1046
+ 使用 StreamingMode.SSE 启用真正的流式 token 输出
1047
+ """
1048
+ from google.adk.agents.run_config import RunConfig, StreamingMode
1049
+ from google.genai import types
1050
+
1051
+ user_input = input_data.get("input", "")
1052
+ instructions = str(input_data.get("instructions") or "").strip()
1053
+ if instructions:
1054
+ user_input = f"{instructions}\n\nCurrent user input:\n{user_input or '[empty message]'}"
1055
+
1056
+ # 1. 准备 Metadata (提前以此获取 Agent Name)
1057
+ _, _, _, agent_name = self._prepare_trace_metadata(None)
1058
+ trace_name = agent_name or "adk.stream"
1059
+
1060
+ with tracer.start_as_current_span(trace_name) as span:
1061
+ # Set input.value for Langfuse top-level input display
1062
+ span.set_attribute("input.value", user_input)
1063
+ span.set_attribute("user.input", user_input[:200])
1064
+
1065
+ # Use external session ID if provided
1066
+ req_session_id = input_data.get("session_id")
1067
+ session_id = await self._ensure_session(req_session_id)
1068
+
1069
+ # 准备 Metadata 并设置 Span Attributes
1070
+ agent_user_id, tags, _, _ = self._prepare_trace_metadata(session_id)
1071
+
1072
+ span.set_attribute("langfuse.session_id", session_id)
1073
+ if agent_user_id:
1074
+ span.set_attribute("langfuse.user_id", agent_user_id)
1075
+ if tags:
1076
+ span.set_attribute("langfuse.tags", ",".join(tags))
1077
+
1078
+ new_message = self._build_adk_content(
1079
+ user_input,
1080
+ input_data.get("attachments", []),
1081
+ model_metadata=input_data.get("model_metadata"),
1082
+ )
1083
+ state_delta = self._build_state_delta(input_data)
1084
+
1085
+ accumulated_text = ""
1086
+ usage: dict[str, Any] = {}
1087
+ last_usage: dict[str, Any] = {}
1088
+
1089
+ # 使用 StreamingMode.SSE 启用真正的流式输出
1090
+ run_config = RunConfig(streaming_mode=StreamingMode.SSE)
1091
+
1092
+ async for event in self._runner.run_async(
1093
+ session_id=session_id,
1094
+ user_id="ksadk_user",
1095
+ new_message=new_message,
1096
+ state_delta=state_delta or None,
1097
+ run_config=run_config,
1098
+ ):
1099
+ event_usage = self._extract_event_usage(event)
1100
+ if event_usage:
1101
+ usage = accumulate_usage(usage, event_usage)
1102
+ last_usage = event_usage # 保留最后一个非空
1103
+ # Only yield text delta if event is partial to avoid duplication of final summary
1104
+ if hasattr(event, "content") and event.content and getattr(event, "partial", False):
1105
+ if hasattr(event.content, "parts"):
1106
+ for part in event.content.parts:
1107
+ if hasattr(part, "text") and part.text:
1108
+ is_thought = getattr(part, "thought", False)
1109
+ accumulated_text += part.text
1110
+ # 标记思考内容,前端可以选择是否展示
1111
+ yield {
1112
+ "delta": part.text,
1113
+ "type": "thinking" if is_thought else "text",
1114
+ }
1115
+
1116
+ # 处理工具调用事件
1117
+ if hasattr(event, "actions") and event.actions:
1118
+ tool_calls = getattr(event.actions, "tool_calls", None)
1119
+ if tool_calls:
1120
+ for tool_call in tool_calls:
1121
+ yield {
1122
+ "type": "tool_call",
1123
+ "tool_name": getattr(tool_call, "name", "unknown"),
1124
+ "tool_args": getattr(tool_call, "input", {}),
1125
+ }
1126
+
1127
+ # Set output.value for Langfuse top-level output display
1128
+ span.set_attribute("output.value", accumulated_text[:5000] if accumulated_text else "")
1129
+ span.set_attribute("agent.output", accumulated_text[:500])
1130
+ final_chunk: dict[str, Any] = {"output": accumulated_text, "type": "final"}
1131
+ if usage:
1132
+ final_chunk["usage"] = usage
1133
+ # last_usage = 最后一次 LLM 调用快照(input_tokens = 当前上下文窗口占用)
1134
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
1135
+ yield final_chunk