aip-agents-binary 0.0.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 (447) hide show
  1. aip_agents/__init__.py +66 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1125 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/a2ui/__init__.py +26 -0
  10. aip_agents/a2ui/catalog_provider.py +109 -0
  11. aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
  12. aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
  13. aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
  14. aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
  15. aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
  16. aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
  17. aip_agents/a2ui/middleware.py +754 -0
  18. aip_agents/a2ui/models.py +153 -0
  19. aip_agents/a2ui/public_url.py +138 -0
  20. aip_agents/a2ui/stream_filter.py +67 -0
  21. aip_agents/agent/__init__.py +72 -0
  22. aip_agents/agent/base_agent.py +1437 -0
  23. aip_agents/agent/base_langgraph_agent.py +4048 -0
  24. aip_agents/agent/google_adk_agent.py +932 -0
  25. aip_agents/agent/google_adk_constants.py +11 -0
  26. aip_agents/agent/graph_runtime.py +185 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/config.py +28 -0
  29. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  30. aip_agents/agent/hitl/manager.py +532 -0
  31. aip_agents/agent/hitl/models.py +18 -0
  32. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  33. aip_agents/agent/hitl/prompt/base.py +42 -0
  34. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  35. aip_agents/agent/hitl/registry.py +149 -0
  36. aip_agents/agent/interface.py +138 -0
  37. aip_agents/agent/interfaces.py +65 -0
  38. aip_agents/agent/langflow_agent.py +464 -0
  39. aip_agents/agent/langgraph_react_agent.py +3741 -0
  40. aip_agents/agent/model_invocation_adapter.py +346 -0
  41. aip_agents/agent/system_instruction_context.py +34 -0
  42. aip_agents/agent/tool_execution_engine.py +587 -0
  43. aip_agents/audio_interface/__init__.py +30 -0
  44. aip_agents/audio_interface/audio_agent_adapter.py +254 -0
  45. aip_agents/audio_interface/config.py +146 -0
  46. aip_agents/audio_interface/errors.py +15 -0
  47. aip_agents/audio_interface/livekit_audio_session.py +504 -0
  48. aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
  49. aip_agents/audio_interface/session_factory.py +40 -0
  50. aip_agents/clients/__init__.py +10 -0
  51. aip_agents/clients/langflow/__init__.py +10 -0
  52. aip_agents/clients/langflow/client.py +477 -0
  53. aip_agents/clients/langflow/types.py +18 -0
  54. aip_agents/common/__init__.py +5 -0
  55. aip_agents/common/callable_introspection.py +17 -0
  56. aip_agents/constants.py +23 -0
  57. aip_agents/context/__init__.py +5 -0
  58. aip_agents/context/budget.py +237 -0
  59. aip_agents/context/compaction.py +339 -0
  60. aip_agents/context/metadata.py +169 -0
  61. aip_agents/context/offload.py +449 -0
  62. aip_agents/context/preparation.py +196 -0
  63. aip_agents/context/prompt.py +61 -0
  64. aip_agents/context/runtime.py +149 -0
  65. aip_agents/context/session_recall.py +69 -0
  66. aip_agents/context/state.py +48 -0
  67. aip_agents/context/utils.py +44 -0
  68. aip_agents/credentials/manager.py +132 -0
  69. aip_agents/examples/__init__.py +5 -0
  70. aip_agents/examples/audio_livekit_session.py +116 -0
  71. aip_agents/examples/compare_streaming_client.py +783 -0
  72. aip_agents/examples/compare_streaming_server.py +141 -0
  73. aip_agents/examples/gl_connectors_smoke.py +148 -0
  74. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  75. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
  78. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  79. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  80. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  81. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  82. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  83. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
  84. aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
  85. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
  86. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  87. aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
  88. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  89. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  90. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
  91. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  92. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  93. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  94. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  95. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  96. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  97. aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
  98. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
  99. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
  100. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  101. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  102. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
  103. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  104. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
  105. aip_agents/examples/hello_world_filesystem.py +162 -0
  106. aip_agents/examples/hello_world_google_adk.py +41 -0
  107. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  108. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  109. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  110. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  111. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  112. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  113. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  114. aip_agents/examples/hello_world_langchain.py +28 -0
  115. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  116. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  117. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  118. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  119. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  120. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  121. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  122. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  123. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  124. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  125. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  126. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  127. aip_agents/examples/hello_world_langgraph.py +39 -0
  128. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
  129. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  130. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  131. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  132. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  133. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  134. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  135. aip_agents/examples/hello_world_langgraph_skills.py +62 -0
  136. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  137. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  138. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  139. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  140. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  141. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  142. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  143. aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
  144. aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
  145. aip_agents/examples/hello_world_pii_logger.py +21 -0
  146. aip_agents/examples/hello_world_ptc.py +49 -0
  147. aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
  148. aip_agents/examples/hello_world_sentry.py +133 -0
  149. aip_agents/examples/hello_world_step_limits.py +273 -0
  150. aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
  151. aip_agents/examples/hello_world_tool_output_client.py +55 -0
  152. aip_agents/examples/hello_world_tool_output_server.py +113 -0
  153. aip_agents/examples/hitl_demo.py +724 -0
  154. aip_agents/examples/mcp_configs/configs.py +63 -0
  155. aip_agents/examples/mcp_servers/common.py +76 -0
  156. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  157. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  158. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  159. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  160. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  161. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  162. aip_agents/examples/pii_demo_langgraph_server.py +125 -0
  163. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  164. aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
  165. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  166. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
  167. aip_agents/examples/tools/__init__.py +27 -0
  168. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  169. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  170. aip_agents/examples/tools/data_generator_tool.py +103 -0
  171. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  172. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  173. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  174. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  175. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  176. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  177. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  178. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  179. aip_agents/examples/tools/multiply_tool.py +43 -0
  180. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  181. aip_agents/examples/tools/random_chart_tool.py +142 -0
  182. aip_agents/examples/tools/serper_tool.py +202 -0
  183. aip_agents/examples/tools/stock_tools.py +82 -0
  184. aip_agents/examples/tools/table_generator_tool.py +167 -0
  185. aip_agents/examples/tools/time_tool.py +82 -0
  186. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  187. aip_agents/guardrails/__init__.py +83 -0
  188. aip_agents/guardrails/engines/__init__.py +69 -0
  189. aip_agents/guardrails/engines/base.py +90 -0
  190. aip_agents/guardrails/engines/nemo.py +101 -0
  191. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  192. aip_agents/guardrails/exceptions.py +39 -0
  193. aip_agents/guardrails/manager.py +163 -0
  194. aip_agents/guardrails/middleware.py +199 -0
  195. aip_agents/guardrails/schemas.py +63 -0
  196. aip_agents/guardrails/utils.py +45 -0
  197. aip_agents/integration/__init__.py +43 -0
  198. aip_agents/integration/agent.py +58 -0
  199. aip_agents/integration/guardrails.py +52 -0
  200. aip_agents/integration/hitl.py +71 -0
  201. aip_agents/integration/ptc.py +95 -0
  202. aip_agents/integration/skills.py +37 -0
  203. aip_agents/integration/storage.py +81 -0
  204. aip_agents/integration/version.py +103 -0
  205. aip_agents/mcp/__init__.py +1 -0
  206. aip_agents/mcp/client/__init__.py +60 -0
  207. aip_agents/mcp/client/base_mcp_client.py +377 -0
  208. aip_agents/mcp/client/connection_manager.py +231 -0
  209. aip_agents/mcp/client/errors.py +119 -0
  210. aip_agents/mcp/client/google_adk/__init__.py +45 -0
  211. aip_agents/mcp/client/google_adk/client.py +383 -0
  212. aip_agents/mcp/client/langchain/__init__.py +11 -0
  213. aip_agents/mcp/client/langchain/client.py +265 -0
  214. aip_agents/mcp/client/persistent_session.py +704 -0
  215. aip_agents/mcp/client/session_pool.py +358 -0
  216. aip_agents/mcp/client/transports.py +288 -0
  217. aip_agents/mcp/utils/__init__.py +7 -0
  218. aip_agents/mcp/utils/config_validator.py +139 -0
  219. aip_agents/memory/__init__.py +14 -0
  220. aip_agents/memory/adapters/__init__.py +10 -0
  221. aip_agents/memory/adapters/base_adapter.py +815 -0
  222. aip_agents/memory/adapters/mem0.py +286 -0
  223. aip_agents/memory/base.py +84 -0
  224. aip_agents/memory/constants.py +49 -0
  225. aip_agents/memory/factory.py +86 -0
  226. aip_agents/memory/guidance.py +20 -0
  227. aip_agents/memory/simple_memory.py +47 -0
  228. aip_agents/middleware/__init__.py +65 -0
  229. aip_agents/middleware/backends/__init__.py +72 -0
  230. aip_agents/middleware/backends/in_memory.py +578 -0
  231. aip_agents/middleware/backends/local_disk.py +1203 -0
  232. aip_agents/middleware/backends/protocol.py +353 -0
  233. aip_agents/middleware/backends/sandbox.py +1431 -0
  234. aip_agents/middleware/backends/sandbox_files.py +113 -0
  235. aip_agents/middleware/backends/utils.py +293 -0
  236. aip_agents/middleware/base.py +239 -0
  237. aip_agents/middleware/filesystem.py +690 -0
  238. aip_agents/middleware/manager.py +291 -0
  239. aip_agents/middleware/memory.py +532 -0
  240. aip_agents/middleware/pii_middleware.py +192 -0
  241. aip_agents/middleware/schema.py +19 -0
  242. aip_agents/middleware/skills.py +872 -0
  243. aip_agents/middleware/task_delegation.py +1656 -0
  244. aip_agents/middleware/todolist.py +276 -0
  245. aip_agents/middleware/tools/__init__.py +23 -0
  246. aip_agents/middleware/tools/edit_file.py +70 -0
  247. aip_agents/middleware/tools/execute.py +428 -0
  248. aip_agents/middleware/tools/grep_file.py +79 -0
  249. aip_agents/middleware/tools/ls.py +71 -0
  250. aip_agents/middleware/tools/read_file.py +239 -0
  251. aip_agents/middleware/tools/skill_load.py +193 -0
  252. aip_agents/middleware/tools/skill_resource.py +338 -0
  253. aip_agents/middleware/tools/write_file.py +114 -0
  254. aip_agents/middleware/vision.py +322 -0
  255. aip_agents/ptc/__init__.py +87 -0
  256. aip_agents/ptc/custom_tools.py +511 -0
  257. aip_agents/ptc/custom_tools_payload.py +499 -0
  258. aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
  259. aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
  260. aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
  261. aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
  262. aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
  263. aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
  264. aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
  265. aip_agents/ptc/doc_gen.py +122 -0
  266. aip_agents/ptc/exceptions.py +57 -0
  267. aip_agents/ptc/executor.py +627 -0
  268. aip_agents/ptc/mcp/__init__.py +45 -0
  269. aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
  270. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  271. aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
  272. aip_agents/ptc/naming.py +430 -0
  273. aip_agents/ptc/payload.py +26 -0
  274. aip_agents/ptc/prompt_builder.py +674 -0
  275. aip_agents/ptc/ptc_helper.py +16 -0
  276. aip_agents/ptc/sandbox_bridge.py +297 -0
  277. aip_agents/ptc/template_utils.py +33 -0
  278. aip_agents/ptc/templates/__init__.py +1 -0
  279. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  280. aip_agents/ptc/tool_def_helpers.py +101 -0
  281. aip_agents/ptc/tool_enrichment.py +163 -0
  282. aip_agents/resilience/__init__.py +10 -0
  283. aip_agents/resilience/base.py +46 -0
  284. aip_agents/resilience/tool.py +1661 -0
  285. aip_agents/sandbox/__init__.py +62 -0
  286. aip_agents/sandbox/defaults.py +220 -0
  287. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
  288. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
  289. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
  290. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
  291. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
  292. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
  293. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
  294. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
  295. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
  296. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
  297. aip_agents/sandbox/e2b_runtime.py +27 -0
  298. aip_agents/sandbox/image_registry.py +98 -0
  299. aip_agents/sandbox/providers/__init__.py +76 -0
  300. aip_agents/sandbox/providers/base.py +392 -0
  301. aip_agents/sandbox/providers/bedrock.py +28 -0
  302. aip_agents/sandbox/providers/e2b.py +230 -0
  303. aip_agents/sandbox/providers/opensandbox.py +163 -0
  304. aip_agents/sandbox/sandbox_runtime.py +967 -0
  305. aip_agents/sandbox/template_builder.py +240 -0
  306. aip_agents/sandbox/types.py +141 -0
  307. aip_agents/sandbox/validation.py +50 -0
  308. aip_agents/schema/__init__.py +106 -0
  309. aip_agents/schema/a2a.py +58 -0
  310. aip_agents/schema/agent.py +117 -0
  311. aip_agents/schema/artifacts.py +75 -0
  312. aip_agents/schema/attachments.py +425 -0
  313. aip_agents/schema/hitl.py +157 -0
  314. aip_agents/schema/langgraph.py +47 -0
  315. aip_agents/schema/model_id.py +103 -0
  316. aip_agents/schema/model_usage.py +50 -0
  317. aip_agents/schema/runtime_metadata.py +78 -0
  318. aip_agents/schema/step_limit.py +108 -0
  319. aip_agents/schema/storage.py +40 -0
  320. aip_agents/schema/tool_payloads.py +68 -0
  321. aip_agents/sentry/__init__.py +11 -0
  322. aip_agents/sentry/sentry.py +267 -0
  323. aip_agents/skills/__init__.py +30 -0
  324. aip_agents/skills/errors.py +110 -0
  325. aip_agents/skills/installer.py +479 -0
  326. aip_agents/skills/models.py +769 -0
  327. aip_agents/skills/stager.py +224 -0
  328. aip_agents/skills/validation.py +53 -0
  329. aip_agents/storage/__init__.py +41 -0
  330. aip_agents/storage/base.py +85 -0
  331. aip_agents/storage/clients/__init__.py +12 -0
  332. aip_agents/storage/clients/minio_client.py +318 -0
  333. aip_agents/storage/config.py +62 -0
  334. aip_agents/storage/providers/__init__.py +15 -0
  335. aip_agents/storage/providers/base.py +106 -0
  336. aip_agents/storage/providers/memory.py +114 -0
  337. aip_agents/storage/providers/object_storage.py +214 -0
  338. aip_agents/tools/__init__.py +65 -0
  339. aip_agents/tools/agent_schedule_tool.py +392 -0
  340. aip_agents/tools/browser_use/__init__.py +83 -0
  341. aip_agents/tools/browser_use/action_parser.py +104 -0
  342. aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
  343. aip_agents/tools/browser_use/llm_config.py +120 -0
  344. aip_agents/tools/browser_use/minio_storage.py +203 -0
  345. aip_agents/tools/browser_use/schemas.py +121 -0
  346. aip_agents/tools/browser_use/session.py +13 -0
  347. aip_agents/tools/browser_use/session_errors.py +132 -0
  348. aip_agents/tools/browser_use/steel_session_recording.py +321 -0
  349. aip_agents/tools/browser_use/streaming.py +263 -0
  350. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  351. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  352. aip_agents/tools/browser_use/types.py +37 -0
  353. aip_agents/tools/code_sandbox/__init__.py +48 -0
  354. aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
  355. aip_agents/tools/code_sandbox/constant.py +13 -0
  356. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
  357. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
  358. aip_agents/tools/computer_use/__init__.py +75 -0
  359. aip_agents/tools/computer_use/computer_use_tool.py +910 -0
  360. aip_agents/tools/computer_use/schemas.py +78 -0
  361. aip_agents/tools/computer_use/streaming.py +198 -0
  362. aip_agents/tools/computer_use/types.py +32 -0
  363. aip_agents/tools/constants.py +177 -0
  364. aip_agents/tools/date_range_tool.py +554 -0
  365. aip_agents/tools/document_loader/__init__.py +44 -0
  366. aip_agents/tools/document_loader/base_reader.py +302 -0
  367. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  368. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  369. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  370. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  371. aip_agents/tools/execute_ptc_code.py +582 -0
  372. aip_agents/tools/gl_connector/__init__.py +5 -0
  373. aip_agents/tools/gl_connector/tool.py +518 -0
  374. aip_agents/tools/gl_connector_tools.py +170 -0
  375. aip_agents/tools/memory_search/__init__.py +29 -0
  376. aip_agents/tools/memory_search/base.py +213 -0
  377. aip_agents/tools/memory_search/mem0.py +424 -0
  378. aip_agents/tools/memory_search/schema.py +81 -0
  379. aip_agents/tools/memory_search_tool.py +34 -0
  380. aip_agents/tools/streaming_common.py +577 -0
  381. aip_agents/tools/time_tool.py +117 -0
  382. aip_agents/tools/tool_config_injector.py +300 -0
  383. aip_agents/tools/web_search/__init__.py +15 -0
  384. aip_agents/tools/web_search/serper_tool.py +187 -0
  385. aip_agents/types/__init__.py +73 -0
  386. aip_agents/types/a2a_events.py +13 -0
  387. aip_agents/utils/__init__.py +91 -0
  388. aip_agents/utils/a2a_connector.py +1758 -0
  389. aip_agents/utils/artifact_helpers.py +542 -0
  390. aip_agents/utils/artifacts/__init__.py +20 -0
  391. aip_agents/utils/artifacts/tracker.py +367 -0
  392. aip_agents/utils/attachment_helpers.py +178 -0
  393. aip_agents/utils/constants.py +22 -0
  394. aip_agents/utils/datetime/__init__.py +34 -0
  395. aip_agents/utils/datetime/normalization.py +231 -0
  396. aip_agents/utils/datetime/timezone.py +206 -0
  397. aip_agents/utils/env_loader.py +27 -0
  398. aip_agents/utils/event_handler_registry.py +58 -0
  399. aip_agents/utils/file_prompt_utils.py +276 -0
  400. aip_agents/utils/file_watchers/__init__.py +33 -0
  401. aip_agents/utils/file_watchers/base.py +46 -0
  402. aip_agents/utils/file_watchers/e2b.py +128 -0
  403. aip_agents/utils/file_watchers/factory.py +39 -0
  404. aip_agents/utils/file_watchers/ls_diff.py +88 -0
  405. aip_agents/utils/file_watchers/sandbox.py +66 -0
  406. aip_agents/utils/file_watchers/watchdog.py +97 -0
  407. aip_agents/utils/final_response_builder.py +211 -0
  408. aip_agents/utils/formatter_llm_client.py +258 -0
  409. aip_agents/utils/langgraph/__init__.py +19 -0
  410. aip_agents/utils/langgraph/converter.py +399 -0
  411. aip_agents/utils/langgraph/delegation_context.py +22 -0
  412. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  413. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  414. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  415. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
  416. aip_agents/utils/langgraph/tool_output_management.py +1047 -0
  417. aip_agents/utils/logger.py +195 -0
  418. aip_agents/utils/metadata/__init__.py +27 -0
  419. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  420. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  421. aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
  422. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  423. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  424. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  425. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  426. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  427. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  428. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  429. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  430. aip_agents/utils/metadata_helper.py +363 -0
  431. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  432. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  433. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  434. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  435. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  436. aip_agents/utils/pii/__init__.py +29 -0
  437. aip_agents/utils/pii/pii_handler.py +555 -0
  438. aip_agents/utils/pii/pii_helper.py +225 -0
  439. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  440. aip_agents/utils/reference_helper.py +273 -0
  441. aip_agents/utils/sse_chunk_transformer.py +836 -0
  442. aip_agents/utils/step_limit_manager.py +265 -0
  443. aip_agents/utils/token_usage_helper.py +531 -0
  444. aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
  445. aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
  446. aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
  447. aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,237 @@
1
+ """Context budget and compaction decision helpers.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Sequence
10
+ from dataclasses import dataclass
11
+ from typing import Any, Literal
12
+
13
+ from aip_agents.context.state import SummaryInsertionPoint
14
+
15
+ _CHARS_PER_TOKEN = 4
16
+ _BASE_TOKENS_PER_MESSAGE = 4
17
+ _IMAGE_PART_TOKEN_ESTIMATE = 256
18
+
19
+ CompactionReason = Literal[
20
+ "below_threshold",
21
+ "proactive_threshold",
22
+ "fallback_threshold",
23
+ "manual_focus",
24
+ "provider_overflow",
25
+ "anti_thrash_skip",
26
+ ]
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ContextBudgetConfig:
31
+ """Configuration for context budget decisions.
32
+
33
+ Attributes:
34
+ threshold_ratio: Fraction of the model context that triggers proactive compaction.
35
+ fallback_threshold_tokens: Absolute threshold when the model context window is unknown.
36
+ min_tail_tokens: Minimum recent-token budget preserved after compaction.
37
+ target_tail_ratio: Fraction of estimated prompt tokens preserved as recent tail.
38
+ min_head_messages: Number of initial messages always preserved.
39
+ anti_thrash_min_savings_ratio: Minimum useful savings before repeated compaction.
40
+ max_retry_after_overflow: Runtime retry cap consumed by overflow handling.
41
+ """
42
+
43
+ threshold_ratio: float = 0.85
44
+ fallback_threshold_tokens: int = 170_000
45
+ min_tail_tokens: int = 6_000
46
+ target_tail_ratio: float = 0.10
47
+ min_head_messages: int = 2
48
+ anti_thrash_min_savings_ratio: float = 0.10
49
+ max_retry_after_overflow: int = 1
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class CompactionDecision:
54
+ """Result of pure compaction decision-making.
55
+
56
+ Attributes:
57
+ should_compact: Whether the current input should enter the compaction path.
58
+ reason: Stable reason code for logging, tests, and retry behavior.
59
+ estimated_prompt_tokens: Estimated prompt size before compaction.
60
+ model_context_tokens: Model context window used for thresholding, if known.
61
+ threshold_tokens: Effective token threshold used by this decision.
62
+ protected_head_indexes: Initial message indexes excluded from replacement.
63
+ protected_tail_start_index: First recent-tail message index to preserve.
64
+ focus_topic: Optional user-provided focus topic for manual compaction.
65
+ summary_insertion_point: Replaceable range where a summary can be inserted.
66
+ """
67
+
68
+ should_compact: bool
69
+ reason: CompactionReason
70
+ estimated_prompt_tokens: int
71
+ model_context_tokens: int | None
72
+ threshold_tokens: int
73
+ protected_head_indexes: tuple[int, ...]
74
+ protected_tail_start_index: int
75
+ focus_topic: str | None = None
76
+ summary_insertion_point: SummaryInsertionPoint | None = None
77
+
78
+
79
+ def estimate_message_token_counts(messages: Sequence[Any]) -> list[int]:
80
+ """Estimate token counts per message with conservative heuristics."""
81
+ return [_estimate_single_message_tokens(message) for message in messages]
82
+
83
+
84
+ def estimate_prompt_tokens(messages: Sequence[Any]) -> int:
85
+ """Estimate total prompt tokens from message sequence."""
86
+ return sum(estimate_message_token_counts(messages))
87
+
88
+
89
+ def compute_protected_regions(
90
+ token_counts: Sequence[int],
91
+ config: ContextBudgetConfig | None = None,
92
+ ) -> tuple[tuple[int, ...], int]:
93
+ """Compute protected head indexes and token-budget tail start index."""
94
+ cfg = config or ContextBudgetConfig()
95
+ if not token_counts:
96
+ return (), 0
97
+
98
+ message_count = len(token_counts)
99
+ head_count = min(cfg.min_head_messages, message_count)
100
+ protected_head_indexes = tuple(range(head_count))
101
+
102
+ total_tokens = sum(token_counts)
103
+ tail_target_tokens = max(cfg.min_tail_tokens, int(total_tokens * cfg.target_tail_ratio))
104
+
105
+ running = 0
106
+ tail_start = message_count
107
+ for index in range(message_count - 1, -1, -1):
108
+ running += token_counts[index]
109
+ tail_start = index
110
+ if running >= tail_target_tokens:
111
+ break
112
+
113
+ if tail_start < head_count:
114
+ tail_start = head_count
115
+
116
+ return protected_head_indexes, tail_start
117
+
118
+
119
+ def classify_provider_overflow_error(error: Exception | str) -> bool:
120
+ """Return whether an error appears to be a context-window overflow."""
121
+ text = str(error).lower()
122
+ patterns = (
123
+ "context window",
124
+ "maximum context length",
125
+ "too many tokens",
126
+ "prompt is too long",
127
+ "context_length_exceeded",
128
+ "input length exceeds",
129
+ "token limit",
130
+ "context limit",
131
+ )
132
+ return any(pattern in text for pattern in patterns)
133
+
134
+
135
+ def decide_compaction(
136
+ messages: Sequence[Any],
137
+ *,
138
+ config: ContextBudgetConfig | None = None,
139
+ model_context_tokens: int | None = None,
140
+ focus_topic: str | None = None,
141
+ provider_overflow: bool = False,
142
+ recent_savings_ratio: float | None = None,
143
+ ) -> CompactionDecision:
144
+ """Return a pure compaction decision from budget inputs.
145
+
146
+ This function does not call models and does not mutate runtime state.
147
+ """
148
+ cfg = config or ContextBudgetConfig()
149
+ token_counts = estimate_message_token_counts(messages)
150
+ estimated_tokens = sum(token_counts)
151
+ threshold_tokens = (
152
+ max(1, int(model_context_tokens * cfg.threshold_ratio))
153
+ if model_context_tokens is not None
154
+ else cfg.fallback_threshold_tokens
155
+ )
156
+
157
+ protected_head_indexes, protected_tail_start_index = compute_protected_regions(token_counts, config=cfg)
158
+ replace_start_index = len(protected_head_indexes)
159
+ replace_end_index = protected_tail_start_index - 1
160
+ insertion_point = None
161
+ if replace_start_index <= replace_end_index:
162
+ insertion_point = SummaryInsertionPoint(
163
+ protected_head_indexes=protected_head_indexes,
164
+ protected_tail_start_index=protected_tail_start_index,
165
+ replace_start_index=replace_start_index,
166
+ replace_end_index=replace_end_index,
167
+ )
168
+
169
+ reason: CompactionReason
170
+ should_compact = False
171
+
172
+ normalized_focus_topic = (focus_topic or "").strip() or None
173
+ if normalized_focus_topic is not None:
174
+ reason = "manual_focus"
175
+ should_compact = True
176
+ elif provider_overflow:
177
+ reason = "provider_overflow"
178
+ should_compact = True
179
+ elif estimated_tokens > threshold_tokens:
180
+ reason = "proactive_threshold" if model_context_tokens is not None else "fallback_threshold"
181
+ should_compact = True
182
+ elif recent_savings_ratio is not None and recent_savings_ratio < cfg.anti_thrash_min_savings_ratio:
183
+ reason = "anti_thrash_skip"
184
+ should_compact = False
185
+ else:
186
+ reason = "below_threshold"
187
+
188
+ return CompactionDecision(
189
+ should_compact=should_compact,
190
+ reason=reason,
191
+ estimated_prompt_tokens=estimated_tokens,
192
+ model_context_tokens=model_context_tokens,
193
+ threshold_tokens=threshold_tokens,
194
+ protected_head_indexes=protected_head_indexes,
195
+ protected_tail_start_index=protected_tail_start_index,
196
+ focus_topic=normalized_focus_topic,
197
+ summary_insertion_point=insertion_point,
198
+ )
199
+
200
+
201
+ def _estimate_single_message_tokens(message: Any) -> int:
202
+ if isinstance(message, str):
203
+ content = message
204
+ elif isinstance(message, dict) and "content" in message:
205
+ content = message["content"]
206
+ else:
207
+ content = getattr(message, "content", message)
208
+
209
+ if isinstance(content, list):
210
+ return _BASE_TOKENS_PER_MESSAGE + sum(_estimate_content_part_tokens(part) for part in content)
211
+
212
+ text = str(content or "")
213
+ return _BASE_TOKENS_PER_MESSAGE + _estimate_text_tokens(text, _CHARS_PER_TOKEN)
214
+
215
+
216
+ def _estimate_content_part_tokens(part: Any) -> int:
217
+ if isinstance(part, dict):
218
+ part_type = str(part.get("type", "")).lower()
219
+ if "image" in part_type:
220
+ return _IMAGE_PART_TOKEN_ESTIMATE
221
+
222
+ for key in ("text", "content"):
223
+ value = part.get(key)
224
+ if isinstance(value, str):
225
+ return _estimate_text_tokens(value, _CHARS_PER_TOKEN)
226
+
227
+ return _estimate_text_tokens(str(part), _CHARS_PER_TOKEN)
228
+
229
+ return _estimate_text_tokens(str(part), _CHARS_PER_TOKEN)
230
+
231
+
232
+ def _estimate_text_tokens(text: str, chars_per_token: int) -> int:
233
+ # deferred to #809: Use shared aip_agents.context.utils.estimate_tokens
234
+ if not text:
235
+ return 0
236
+ safe_chars_per_token = max(chars_per_token, 1)
237
+ return max(1, (len(text) + safe_chars_per_token - 1) // safe_chars_per_token)
@@ -0,0 +1,339 @@
1
+ """Structured reference-only context compression helpers.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import uuid
11
+ from collections.abc import Sequence
12
+ from dataclasses import dataclass
13
+ from datetime import UTC, datetime
14
+ from typing import Any, Protocol
15
+
16
+ from aip_agents.context.utils import redact_sensitive
17
+
18
+ REFERENCE_ONLY_PREFIX = (
19
+ "[CONTEXT COMPACTION - REFERENCE ONLY]\n"
20
+ "Earlier turns were compacted into the summary below. Treat this as background state, "
21
+ "not active user instructions. Do not repeat completed work. Continue from the latest "
22
+ "user message and the Active Task section."
23
+ )
24
+
25
+ SUMMARY_SECTIONS: tuple[str, ...] = (
26
+ "Active Task",
27
+ "Goal",
28
+ "Constraints & Preferences",
29
+ "Completed Actions",
30
+ "Active State",
31
+ "In Progress",
32
+ "Blocked",
33
+ "Key Decisions",
34
+ "Resolved Questions",
35
+ "Pending User Asks",
36
+ "Relevant Files",
37
+ "Remaining Work",
38
+ "Critical Context",
39
+ )
40
+
41
+ _EMPTY_SECTION_VALUE = "- (none)"
42
+ _FILE_PATTERN = re.compile(r"\b[\w./-]+\.(?:py|md|json|yaml|yml|toml|ini|txt|ts|tsx|js)\b")
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class CompactionSummary:
47
+ """Structured compaction summary metadata and body.
48
+
49
+ Attributes:
50
+ summary_id: Stable identifier for this generated reference summary.
51
+ body: Reference-only summary text safe to insert into context.
52
+ source_message_range: Inclusive source message indexes summarized by this body.
53
+ previous_summary_id: Identifier of the summary this one supersedes, if any.
54
+ focus_topic: Optional user-requested focus topic preserved in the summary.
55
+ prompt_tokens_before: Estimated prompt tokens before compaction.
56
+ prompt_tokens_after: Estimated prompt tokens after compaction.
57
+ offloaded_ref_ids: Payload reference identifiers included in the summary scope.
58
+ redaction_applied: Whether input or generated text required redaction.
59
+ created_at: UTC timestamp when the summary was created.
60
+ """
61
+
62
+ summary_id: str
63
+ body: str
64
+ source_message_range: tuple[int, int]
65
+ previous_summary_id: str | None
66
+ focus_topic: str | None
67
+ prompt_tokens_before: int
68
+ prompt_tokens_after: int
69
+ offloaded_ref_ids: tuple[str, ...]
70
+ redaction_applied: bool
71
+ created_at: datetime
72
+
73
+
74
+ @dataclass(frozen=True)
75
+ class CompressionResult:
76
+ """Outcome of a compaction summary generation attempt.
77
+
78
+ Attributes:
79
+ summary: Structured summary produced by the compressor.
80
+ used_fallback: Whether deterministic fallback summarization was used.
81
+ error: Safe error text when auxiliary summary generation failed.
82
+ """
83
+
84
+ summary: CompactionSummary
85
+ used_fallback: bool
86
+ error: str | None = None
87
+
88
+
89
+ class SummarizerClient(Protocol):
90
+ """Minimal abstraction for auxiliary summary generation."""
91
+
92
+ def summarize(self, prompt: str) -> str:
93
+ """Return summary text from a redacted prompt input."""
94
+
95
+
96
+ class ContextCompressor:
97
+ """Generate structured reference-only summaries from prior conversation turns."""
98
+
99
+ def __init__(self, summarizer: SummarizerClient | None = None) -> None:
100
+ """Initialize compressor with optional auxiliary summarizer client."""
101
+ self._summarizer = summarizer
102
+
103
+ def compress(
104
+ self,
105
+ messages: Sequence[Any],
106
+ *,
107
+ previous_summary: CompactionSummary | None = None,
108
+ focus_topic: str | None = None,
109
+ offloaded_ref_ids: Sequence[str] = (),
110
+ source_message_range: tuple[int, int] | None = None,
111
+ prompt_tokens_before: int = 0,
112
+ prompt_tokens_after: int = 0,
113
+ ) -> CompressionResult:
114
+ """Create a structured summary with safe fallback behavior.
115
+
116
+ Usage::
117
+
118
+ from aip_agents.context.compaction import ContextCompressor
119
+
120
+ compressor = ContextCompressor() # uses fallback summarization
121
+ result = compressor.compress(messages)
122
+ # result.summary.body contains the structured summary
123
+ # result.used_fallback is True when no external summarizer was set
124
+ """
125
+ redacted_message_texts, message_roles, input_redacted = _prepare_redacted_inputs(messages, previous_summary)
126
+ generated, used_fallback, generation_error, output_redacted = self._generate_summary_text(
127
+ redacted_message_texts
128
+ )
129
+ input_redacted = input_redacted or output_redacted
130
+
131
+ sections = _extract_sections(
132
+ messages=redacted_message_texts,
133
+ message_roles=message_roles,
134
+ generated_summary=generated,
135
+ focus_topic=focus_topic,
136
+ previous_summary=previous_summary,
137
+ )
138
+ body = _render_summary_body(sections)
139
+
140
+ summary_id = f"summary_{uuid.uuid4().hex[:12]}"
141
+ message_range = _resolve_message_range(messages, source_message_range)
142
+ summary = CompactionSummary(
143
+ summary_id=summary_id,
144
+ body=body,
145
+ source_message_range=message_range,
146
+ previous_summary_id=previous_summary.summary_id if previous_summary else None,
147
+ focus_topic=(focus_topic or "").strip() or None,
148
+ prompt_tokens_before=prompt_tokens_before,
149
+ prompt_tokens_after=prompt_tokens_after,
150
+ offloaded_ref_ids=tuple(offloaded_ref_ids),
151
+ redaction_applied=input_redacted,
152
+ created_at=datetime.now(UTC),
153
+ )
154
+ return CompressionResult(summary=summary, used_fallback=used_fallback, error=generation_error)
155
+
156
+ def _generate_summary_text(self, redacted_message_texts: Sequence[str]) -> tuple[str, bool, str | None, bool]:
157
+ redacted_input = "\n".join(redacted_message_texts)
158
+ if self._summarizer is None:
159
+ return "", True, None, False
160
+ try:
161
+ generated_raw = self._summarizer.summarize(redacted_input)
162
+ generated, output_redacted = redact_sensitive(str(generated_raw))
163
+ return generated, False, None, output_redacted
164
+ except Exception as error: # pragma: no cover - behavior exercised by tests
165
+ return "", True, str(error), False
166
+
167
+
168
+ def _extract_sections(
169
+ *,
170
+ messages: Sequence[str],
171
+ message_roles: Sequence[str] = (),
172
+ generated_summary: str,
173
+ focus_topic: str | None,
174
+ previous_summary: CompactionSummary | None,
175
+ ) -> dict[str, str]:
176
+ """Extract section content from messages and optional generated summary."""
177
+ latest_user = _latest_user_message(messages, message_roles)
178
+ decision_lines = _collect_lines(messages, keywords=("decision", "decided", "chose"))
179
+ blocker_lines = _collect_lines(messages, keywords=("block", "blocked", "waiting", "dependency"))
180
+ done_lines = _collect_lines(messages, keywords=("done", "completed", "implemented", "finished"))
181
+ pending_lines = _collect_lines(messages, keywords=("todo", "next", "remaining", "pending"))
182
+ question_lines = _collect_lines(messages, keywords=("?", "question", "clarify"))
183
+ file_hits = sorted({match for text in messages for match in _FILE_PATTERN.findall(text)})
184
+
185
+ sections: dict[str, str] = dict.fromkeys(SUMMARY_SECTIONS, _EMPTY_SECTION_VALUE)
186
+ sections["Active Task"] = f"- {latest_user.strip()[:500]}" if latest_user else _EMPTY_SECTION_VALUE
187
+ sections["Goal"] = f"- Focus on {focus_topic}" if focus_topic else "- Continue current requested scope"
188
+ sections["Constraints & Preferences"] = "- Preserve prior constraints and avoid re-running completed work"
189
+ sections["Completed Actions"] = _format_lines(done_lines)
190
+ sections["Active State"] = "- Compacted reference summary prepared"
191
+ sections["In Progress"] = _format_lines(pending_lines)
192
+ sections["Blocked"] = _format_lines(blocker_lines)
193
+ sections["Key Decisions"] = _format_lines(decision_lines)
194
+ sections["Resolved Questions"] = _EMPTY_SECTION_VALUE
195
+ sections["Pending User Asks"] = _format_lines(question_lines)
196
+ sections["Relevant Files"] = _format_lines([f"`{path}`" for path in file_hits])
197
+ sections["Remaining Work"] = _format_lines(pending_lines)
198
+
199
+ _merge_generated_sections(sections, generated_summary)
200
+
201
+ critical_lines: list[str] = []
202
+ if focus_topic:
203
+ critical_lines.append(f"Focus topic: {focus_topic}")
204
+ if previous_summary is not None:
205
+ critical_lines.append(
206
+ f"Previous summary id: {previous_summary.summary_id}"
207
+ if previous_summary is not None
208
+ else "Previous summary id: none"
209
+ )
210
+ if generated_summary:
211
+ critical_lines.append(generated_summary[:1_000])
212
+ sections["Critical Context"] = _format_lines(critical_lines)
213
+
214
+ return sections
215
+
216
+
217
+ def _collect_lines(messages: Sequence[str], *, keywords: Sequence[str]) -> list[str]:
218
+ matched: list[str] = []
219
+ keyword_set = tuple(keyword.lower() for keyword in keywords)
220
+ for text in messages:
221
+ for line in text.splitlines():
222
+ lowered = line.lower()
223
+ if any(keyword in lowered for keyword in keyword_set):
224
+ cleaned = line.strip()
225
+ if cleaned and cleaned not in matched:
226
+ matched.append(cleaned)
227
+ return matched
228
+
229
+
230
+ def _render_summary_body(sections: dict[str, str]) -> str:
231
+ lines = [REFERENCE_ONLY_PREFIX, ""]
232
+ for section in SUMMARY_SECTIONS:
233
+ lines.append(f"## {section}")
234
+ lines.append(sections.get(section, _EMPTY_SECTION_VALUE))
235
+ lines.append("")
236
+ return "\n".join(lines).strip()
237
+
238
+
239
+ def _prepare_redacted_inputs(
240
+ messages: Sequence[Any], previous_summary: CompactionSummary | None
241
+ ) -> tuple[list[str], list[str], bool]:
242
+ redacted_message_texts: list[str] = []
243
+ message_roles: list[str] = []
244
+ input_redacted = False
245
+ for message in messages:
246
+ redacted_text, redacted = redact_sensitive(_message_to_text(message))
247
+ redacted_message_texts.append(redacted_text)
248
+ input_redacted = input_redacted or redacted
249
+ message_roles.append(str(message.get("role", "")) if isinstance(message, dict) else "")
250
+
251
+ if previous_summary is not None:
252
+ previous_body, prev_redacted = redact_sensitive(previous_summary.body)
253
+ redacted_message_texts = [previous_body] + redacted_message_texts
254
+ message_roles = [""] + message_roles
255
+ input_redacted = input_redacted or prev_redacted
256
+
257
+ return redacted_message_texts, message_roles, input_redacted
258
+
259
+
260
+ def _resolve_message_range(messages: Sequence[Any], source_message_range: tuple[int, int] | None) -> tuple[int, int]:
261
+ if source_message_range is not None:
262
+ return source_message_range
263
+ if messages:
264
+ return (0, len(messages) - 1)
265
+ return (-1, -1)
266
+
267
+
268
+ def _merge_generated_sections(sections: dict[str, str], generated_summary: str) -> None:
269
+ if not generated_summary:
270
+ return
271
+ parsed = _parse_generated_sections(generated_summary)
272
+ for section in SUMMARY_SECTIONS:
273
+ content = parsed.get(section)
274
+ if content:
275
+ sections[section] = content
276
+
277
+
278
+ def _parse_generated_sections(generated_summary: str) -> dict[str, str]:
279
+ """Parse a generated summary for structured sections matching SUMMARY_SECTIONS."""
280
+ parsed: dict[str, str] = {}
281
+ current_section: str | None = None
282
+ current_lines: list[str] = []
283
+ for line in generated_summary.splitlines():
284
+ heading_match = re.match(r"^##\s+(.+)", line.strip())
285
+ if heading_match:
286
+ _flush_parsed_section(parsed, current_section, current_lines)
287
+ heading = heading_match.group(1).strip()
288
+ current_section = heading if heading in SUMMARY_SECTIONS else None
289
+ current_lines = []
290
+ elif current_section:
291
+ stripped = line.strip()
292
+ if stripped:
293
+ cleaned = stripped.removeprefix("- ").strip()
294
+ current_lines.append(cleaned or stripped)
295
+ _flush_parsed_section(parsed, current_section, current_lines)
296
+ return parsed
297
+
298
+
299
+ def _flush_parsed_section(parsed: dict[str, str], current_section: str | None, current_lines: list[str]) -> None:
300
+ if not current_section or not current_lines:
301
+ return
302
+ content = _format_lines(current_lines)
303
+ if content != _EMPTY_SECTION_VALUE:
304
+ parsed[current_section] = content
305
+
306
+
307
+ def _latest_user_message(messages: Sequence[str], message_roles: Sequence[str] = ()) -> str:
308
+ """Return the latest user/human message text from the given sequence."""
309
+ if message_roles:
310
+ for text, role in zip(reversed(messages), reversed(message_roles)):
311
+ if role.lower() in {"user", "human"} and text.strip():
312
+ return text.strip()
313
+ for text in reversed(messages):
314
+ if text.strip():
315
+ return text.strip()
316
+ return ""
317
+
318
+
319
+ def _format_lines(lines: Sequence[str]) -> str:
320
+ if not lines:
321
+ return _EMPTY_SECTION_VALUE
322
+ unique = []
323
+ for line in lines:
324
+ normalized = line.strip()
325
+ if normalized and normalized not in unique:
326
+ unique.append(normalized)
327
+ if not unique:
328
+ return _EMPTY_SECTION_VALUE
329
+ return "\n".join(f"- {line}" for line in unique)
330
+
331
+
332
+ def _message_to_text(message: Any) -> str:
333
+ if isinstance(message, str):
334
+ return message
335
+ if isinstance(message, dict):
336
+ content = message.get("content")
337
+ return str(content or "")
338
+ content = getattr(message, "content", "")
339
+ return str(content or "")