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,29 @@
1
+ """Public API for PII-related utilities.
2
+
3
+ This subpackage groups all helpers for anonymizing/deanonymizing PII used by
4
+ agents and tools.
5
+ """
6
+
7
+ from aip_agents.utils.pii.pii_handler import ToolPIIHandler
8
+ from aip_agents.utils.pii.pii_helper import (
9
+ PII_AGENT_PROMPT_ENV_VAR,
10
+ add_pii_mappings,
11
+ anonymize_final_response_content,
12
+ deanonymize_final_response_content,
13
+ extract_pii_mapping_from_agent_response,
14
+ get_pii_agent_prompt,
15
+ normalize_enable_pii,
16
+ )
17
+ from aip_agents.utils.pii.uuid_deanonymizer_mapping import UUIDDeanonymizerMapping
18
+
19
+ __all__ = [
20
+ "PII_AGENT_PROMPT_ENV_VAR",
21
+ "ToolPIIHandler",
22
+ "add_pii_mappings",
23
+ "anonymize_final_response_content",
24
+ "deanonymize_final_response_content",
25
+ "extract_pii_mapping_from_agent_response",
26
+ "get_pii_agent_prompt",
27
+ "normalize_enable_pii",
28
+ "UUIDDeanonymizerMapping",
29
+ ]
@@ -0,0 +1,555 @@
1
+ """PII handler for masking/demasking at the tool calling level.
2
+
3
+ This module provides the ToolPIIHandler class for handling PII operations
4
+ during tool execution in LangGraph agents. Tag replacement works with the
5
+ mapping supplied by the runner, while advanced NER-powered detection is only
6
+ enabled when NER_API_URL and NER_API_KEY environment variables are set.
7
+
8
+ Authors:
9
+ Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
10
+
11
+ References:
12
+ 1. https://gdplabs.gitbook.io/sdk/tutorials/security-and-privacy/pii-masking#anonymizer-with-ner
13
+ """
14
+
15
+ import os
16
+ import socket
17
+ from enum import StrEnum
18
+ from typing import Any
19
+ from urllib.parse import urlparse, urlunparse
20
+
21
+ import requests
22
+
23
+ try:
24
+ from gllm_privacy.pii_detector import TextAnalyzer, TextAnonymizer
25
+ from gllm_privacy.pii_detector.anonymizer import Operation
26
+ from gllm_privacy.pii_detector.constants import GLLM_PRIVACY_ENTITIES, Entities
27
+ from gllm_privacy.pii_detector.recognizer.gdplabs_ner_api_remote_recognizer import (
28
+ GDPLabsNerApiRemoteRecognizer,
29
+ )
30
+
31
+ _HAS_GLLM_PRIVACY = True
32
+ except ImportError: # pragma: no cover
33
+ TextAnalyzer = Any # type: ignore[assignment]
34
+ TextAnonymizer = Any # type: ignore[assignment]
35
+ GDPLabsNerApiRemoteRecognizer = Any # type: ignore[assignment]
36
+
37
+ class _Operation(StrEnum):
38
+ ANONYMIZE = "ANONYMIZE"
39
+ DEANONYMIZE = "DEANONYMIZE"
40
+
41
+ Operation = _Operation
42
+
43
+ GLLM_PRIVACY_ENTITIES = [] # type: ignore[assignment]
44
+ Entities = None # type: ignore[assignment]
45
+ _HAS_GLLM_PRIVACY = False
46
+
47
+ from aip_agents.utils.logger import get_logger
48
+ from aip_agents.utils.pii.uuid_deanonymizer_mapping import UUIDDeanonymizerMapping
49
+
50
+ logger = get_logger(__name__)
51
+
52
+ NER_API_URL_ENV_VAR = "NER_API_URL"
53
+ NER_API_KEY_ENV_VAR = "NER_API_KEY"
54
+ NER_API_TIMEOUT_ENV_VAR = "NER_API_TIMEOUT"
55
+ NER_API_TIMEOUT_DEFAULT = 10
56
+ if _HAS_GLLM_PRIVACY:
57
+ EXCLUDED_ENTITIES = [Entities.URL.value]
58
+ DEFAULT_SUPPORTED_ENTITIES = [entity for entity in GLLM_PRIVACY_ENTITIES if entity not in EXCLUDED_ENTITIES]
59
+ else:
60
+ EXCLUDED_ENTITIES = []
61
+ DEFAULT_SUPPORTED_ENTITIES = []
62
+
63
+
64
+ def _is_ner_reachable(host: str, port: int, ner_api_url: str, timeout: int) -> tuple[bool, str | None]:
65
+ """Check NER server reachability via two-stage probe: TCP + HTTP health check.
66
+
67
+ Performs two-stage probe to validate NER server health:
68
+ Stage 1: TCP connectivity to host:port (quick check for unreachable servers)
69
+ Stage 2: HTTP GET request to server root to detect unhealthy HTTP services
70
+
71
+ Args:
72
+ host: NER server hostname or IP address
73
+ port: NER server port
74
+ ner_api_url: Full NER API URL (used to construct root URL for health check)
75
+ timeout: Connection timeout in seconds (applies to both TCP and HTTP)
76
+
77
+ Returns:
78
+ Tuple of (reachable, failure_reason):
79
+ - (True, None) if both TCP and HTTP checks succeed
80
+ - (False, reason_str) on any failure with specific failure context
81
+
82
+ Note:
83
+ - Checks the server root path (e.g., https://ner.glair.ai/) instead of the
84
+ API endpoint, since most APIs only accept POST to their endpoints.
85
+ - 2xx-4xx responses indicate the server is healthy and reachable.
86
+ - 5xx responses indicate server errors; the server is unhealthy.
87
+ - Timeouts or connection errors are treated as unreachable.
88
+ """
89
+ # Stage 1: TCP connectivity
90
+ sock = None
91
+ try:
92
+ sock = socket.create_connection((host, port), timeout=timeout)
93
+ except TimeoutError:
94
+ return False, "TCP connection timeout" # pragma: no cover
95
+ except (ConnectionRefusedError, ConnectionResetError):
96
+ return False, "TCP connection refused"
97
+ except OSError as e:
98
+ return False, f"TCP connection failed: {type(e).__name__}"
99
+ finally:
100
+ if sock:
101
+ try:
102
+ sock.close()
103
+ except OSError: # pragma: no cover
104
+ pass # pragma: no cover
105
+
106
+ # Stage 2: HTTP health check (GET to server root, treating any non-5xx response as healthy)
107
+ try:
108
+ # Build root URL (e.g., from "https://ner.glair.ai/analyze" to "https://ner.glair.ai/")
109
+ parsed = urlparse(ner_api_url)
110
+ root_url = urlunparse((parsed.scheme, parsed.netloc, "/", "", "", ""))
111
+
112
+ response = requests.get(root_url, timeout=timeout, allow_redirects=True)
113
+ # Consider any response < 500 as healthy (endpoint exists and server is running)
114
+ # Only 5xx indicates server is unhealthy
115
+ if response.status_code < 500:
116
+ return True, None
117
+ return False, f"HTTP {response.status_code} (server error)"
118
+ except requests.Timeout:
119
+ return False, "HTTP request timeout"
120
+ except requests.ConnectionError as e: # pragma: no cover
121
+ return False, f"HTTP connection failed: {type(e).__name__}" # pragma: no cover
122
+ except requests.RequestException as e: # pragma: no cover
123
+ return False, f"HTTP request failed: {type(e).__name__}" # pragma: no cover
124
+
125
+
126
+ class ToolPIIHandler:
127
+ """Handles PII masking/demasking for tool calling.
128
+
129
+ Tag replacement based on runner-provided mappings always works. Optional
130
+ NER-powered masking/de-masking is only enabled when NER_API_URL and
131
+ NER_API_KEY environment variables are set.
132
+
133
+ Attributes:
134
+ flat_pii_mapping: Flat mapping from runner service (tag → value)
135
+ text_analyzer: GLLM Privacy TextAnalyzer instance
136
+ text_anonymizer: GLLM Privacy TextAnonymizer instance
137
+ enable_ner: Whether NER is enabled
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ pii_mapping: dict[str, str] | None = None,
143
+ ner_api_url: str | None = None,
144
+ ner_api_key: str | None = None,
145
+ ner_api_timeout: int = NER_API_TIMEOUT_DEFAULT,
146
+ ) -> None:
147
+ """Initialize PII handler (private - use create_if_enabled() instead).
148
+
149
+ Initializes GLLM Privacy components (TextAnalyzer, TextAnonymizer) if NER credentials
150
+ are provided. Creates dual recognizers for Indonesian and English languages.
151
+ Pre-loads any existing PII mappings into the anonymizer's internal state.
152
+
153
+ Args:
154
+ pii_mapping: Existing PII mapping from runner service (flat format: tag -> value)
155
+ ner_api_url: NER API endpoint URL
156
+ ner_api_key: NER API authentication key
157
+ ner_api_timeout: Timeout in seconds for NER API requests (default: 10)
158
+
159
+ Raises:
160
+ ValueError: If ner_api_timeout is not a positive integer
161
+ """
162
+ if ner_api_timeout <= 0:
163
+ raise ValueError(f"{NER_API_TIMEOUT_ENV_VAR} must be a positive integer, got {ner_api_timeout}.")
164
+
165
+ self.flat_pii_mapping: dict[str, str] = pii_mapping or {}
166
+ self.enable_ner: bool = bool(ner_api_url and ner_api_key)
167
+ self.text_analyzer: TextAnalyzer | None = None
168
+ self.text_anonymizer: TextAnonymizer | None = None
169
+
170
+ if self.enable_ner and not _HAS_GLLM_PRIVACY:
171
+ logger.warning(
172
+ "NER is configured (NER_API_URL/NER_API_KEY present) but optional dependency 'gllm-privacy' "
173
+ "is not installed. Continuing with NER disabled."
174
+ )
175
+ self.enable_ner = False
176
+
177
+ if self.enable_ner:
178
+ self._initialize_ner(ner_api_url, ner_api_key, ner_api_timeout)
179
+
180
+ def _initialize_ner(self, ner_api_url: str, ner_api_key: str, ner_api_timeout: int) -> None:
181
+ """Initialize NER components after health probe.
182
+
183
+ Performs TCP+HTTP health check, then constructs GLLM Privacy recognizers
184
+ if server is reachable. Sets enable_ner=False and logs warning on probe failure.
185
+
186
+ Args:
187
+ ner_api_url: Full NER API URL
188
+ ner_api_key: NER API authentication key
189
+ ner_api_timeout: Request timeout in seconds
190
+ """
191
+ # Perform two-stage probe (TCP + HTTP) to check NER server reachability
192
+ parsed_url = urlparse(ner_api_url)
193
+ host = str(parsed_url.hostname or "")
194
+ port = parsed_url.port or (443 if parsed_url.scheme == "https" else 80)
195
+
196
+ # Validate host before calling probe to avoid wasting resources on malformed URLs
197
+ if not host:
198
+ logger.warning("NER server unreachable: malformed URL, disabling NER. Continuing with mapping-only mode.")
199
+ self.enable_ner = False
200
+ return
201
+
202
+ reachable, failure_reason = _is_ner_reachable(host, port, ner_api_url, ner_api_timeout)
203
+ if not reachable:
204
+ reason_msg = f": {failure_reason}" if failure_reason else ""
205
+ logger.warning(
206
+ f"NER server unreachable at {host}:{port}{reason_msg}, disabling NER. Continuing with mapping-only mode."
207
+ )
208
+ self.enable_ner = False
209
+ return
210
+
211
+ # Construct recognizers and text processing components
212
+ try:
213
+ headers = {"X-Api-Key": ner_api_key}
214
+
215
+ id_recognizer = GDPLabsNerApiRemoteRecognizer(
216
+ api_url=ner_api_url,
217
+ supported_language="id",
218
+ api_headers=headers,
219
+ api_timeout=ner_api_timeout,
220
+ )
221
+ en_recognizer = GDPLabsNerApiRemoteRecognizer(
222
+ api_url=ner_api_url,
223
+ supported_language="en",
224
+ api_headers=headers,
225
+ api_timeout=ner_api_timeout,
226
+ )
227
+
228
+ self.text_analyzer = TextAnalyzer(additional_recognizers=[id_recognizer, en_recognizer])
229
+
230
+ # Initialize with UUID-based deanonymizer mapping
231
+ uuid_mapping = UUIDDeanonymizerMapping(uuid_length=8)
232
+ self.text_anonymizer = TextAnonymizer(
233
+ text_analyzer=self.text_analyzer,
234
+ deanonymizer_mapping=uuid_mapping,
235
+ )
236
+
237
+ if self.flat_pii_mapping:
238
+ gllm_mapping = self._convert_flat_to_gllm_format(self.flat_pii_mapping)
239
+ self.text_anonymizer._deanonymizer_mapping.update(gllm_mapping, use_uuid_suffix=False)
240
+
241
+ except Exception as e: # noqa: BLE001 # pragma: no cover
242
+ logger.warning(f"Failed to initialize GLLM Privacy components: {e}") # pragma: no cover
243
+ self.enable_ner = False # pragma: no cover
244
+
245
+ @classmethod
246
+ def create_if_enabled(cls, pii_mapping: dict[str, str] | None = None) -> "ToolPIIHandler | None":
247
+ """Create ToolPIIHandler when mappings or NER configuration exist.
248
+
249
+ Reads NER_API_URL, NER_API_KEY, and optionally NER_API_TIMEOUT from environment variables.
250
+ NER_API_TIMEOUT must be a positive integer; defaults to 10 seconds if not specified.
251
+
252
+ Args:
253
+ pii_mapping: Existing PII mapping from runner service
254
+
255
+ Returns:
256
+ ToolPIIHandler instance when mapping or NER config is available, None otherwise
257
+
258
+ Raises:
259
+ ValueError: If NER_API_TIMEOUT is set but cannot be parsed as a positive integer
260
+ """
261
+ ner_api_url = os.getenv(NER_API_URL_ENV_VAR)
262
+ ner_api_key = os.getenv(NER_API_KEY_ENV_VAR)
263
+ ner_api_timeout_str = os.getenv(NER_API_TIMEOUT_ENV_VAR)
264
+
265
+ if ner_api_timeout_str:
266
+ try:
267
+ ner_api_timeout = int(ner_api_timeout_str)
268
+ except ValueError as e:
269
+ raise ValueError(
270
+ f"Invalid value for {NER_API_TIMEOUT_ENV_VAR}: '{ner_api_timeout_str}' is not an integer."
271
+ ) from e
272
+ else:
273
+ ner_api_timeout = NER_API_TIMEOUT_DEFAULT
274
+
275
+ if ner_api_url and ner_api_key:
276
+ return cls(pii_mapping, ner_api_url, ner_api_key, ner_api_timeout)
277
+
278
+ if pii_mapping:
279
+ return cls(pii_mapping)
280
+
281
+ return None
282
+
283
+ @classmethod
284
+ def create_mapping_only(cls, pii_mapping: dict[str, str] | None = None) -> "ToolPIIHandler | None":
285
+ """Create ToolPIIHandler in mapping-only mode (no NER).
286
+
287
+ Args:
288
+ pii_mapping: Existing PII mapping from runner service
289
+
290
+ Returns:
291
+ ToolPIIHandler instance when mapping exists, None otherwise
292
+ """
293
+ if not pii_mapping:
294
+ return None
295
+
296
+ return cls(pii_mapping)
297
+
298
+ def deanonymize_tool_args(self, args: dict[str, Any]) -> dict[str, Any]:
299
+ """Replace PII tags in tool arguments with real values.
300
+
301
+ Recursively processes dictionaries, lists, and strings to replace all PII tags
302
+ (e.g., '<EMAIL_1>') with their corresponding real values from flat_pii_mapping.
303
+
304
+ Args:
305
+ args: Tool arguments that may contain PII tags
306
+
307
+ Returns:
308
+ Arguments with tags replaced by real values
309
+ """
310
+ return self._process_value(args, operation=Operation.DEANONYMIZE)
311
+
312
+ def anonymize(self, output: Any) -> tuple[Any, dict[str, str]]:
313
+ """Mask PII values in any string or dict input.
314
+
315
+ Handles string and dictionary inputs. For strings, uses two-phase anonymization:
316
+ first masks known PII, then detects new PII via NER. For dictionaries, recursively
317
+ processes all string values. Returns updated mapping with any newly discovered PII.
318
+
319
+ Args:
320
+ output: Input that may contain PII values (string, dict, or other)
321
+
322
+ Returns:
323
+ Tuple of (anonymized_output, updated_flat_pii_mapping)
324
+ """
325
+ if isinstance(output, str):
326
+ anonymized, updated_mapping = self._anonymize_text(output)
327
+ return anonymized, updated_mapping
328
+ elif isinstance(output, dict):
329
+ anonymized, updated_mapping = self._anonymize_dict(output)
330
+ return anonymized, updated_mapping
331
+ else:
332
+ # For non-string, non-dict outputs, return as-is
333
+ return output, self.flat_pii_mapping
334
+
335
+ def anonymize_tool_output(self, output: Any) -> tuple[Any, dict[str, str]]:
336
+ """Backward-compatible alias for anonymize()."""
337
+ return self.anonymize(output)
338
+
339
+ def anonymize_command_update(self, update: dict[str, Any]) -> dict[str, Any]:
340
+ """Anonymize user-visible values in a ``Command.update`` dict.
341
+
342
+ Recursively masks PII in strings under ``result``/``metadata``/``messages``/other
343
+ state, while preserving binary artifact payload fields (``data``/``bytes``/
344
+ ``base64``) inside the ``artifacts`` subtree and leaving ``pii_mapping`` untouched
345
+ as the raw tag->value channel.
346
+ """
347
+ return self._anonymize_command_value("", update, in_artifact=False)
348
+
349
+ def _anonymize_command_value(self, key: str, value: Any, *, in_artifact: bool) -> Any:
350
+ """Recursively anonymize one Command.update value (see anonymize_command_update)."""
351
+ if key == "pii_mapping":
352
+ return value
353
+ if in_artifact and key in {"data", "bytes", "base64"}:
354
+ return value
355
+ if isinstance(value, str):
356
+ return self.anonymize(value)[0]
357
+ if isinstance(value, dict):
358
+ return {
359
+ child_key: self._anonymize_command_value(str(child_key), child_value, in_artifact=in_artifact)
360
+ for child_key, child_value in value.items()
361
+ }
362
+ if isinstance(value, list):
363
+ child_in_artifact = key == "artifacts"
364
+ return [self._anonymize_command_value(key, item, in_artifact=child_in_artifact) for item in value]
365
+ return value
366
+
367
+ @staticmethod
368
+ def _convert_flat_to_gllm_format(
369
+ flat_mapping: dict[str, str],
370
+ ) -> dict[str, dict[str, str]]:
371
+ """Convert flat PII mapping to GLLM Privacy nested format.
372
+
373
+ Transforms flat format {'<PERSON_1>': 'Alice'} to nested format
374
+ {'PERSON': {'<PERSON_1>': 'Alice'}} required by GLLM Privacy's internal state.
375
+
376
+ Args:
377
+ flat_mapping: Flat mapping (tag → value)
378
+
379
+ Returns:
380
+ Nested mapping organized by entity type
381
+ """
382
+ gllm_mapping: dict[str, dict[str, str]] = {}
383
+
384
+ for tag, value in flat_mapping.items():
385
+ tag_content = tag.strip("<>")
386
+ parts = tag_content.rsplit("_", 1)
387
+ entity_type = parts[0] if parts else "UNKNOWN"
388
+
389
+ if entity_type not in gllm_mapping:
390
+ gllm_mapping[entity_type] = {}
391
+
392
+ gllm_mapping[entity_type][tag] = value
393
+
394
+ return gllm_mapping
395
+
396
+ @staticmethod
397
+ def _convert_gllm_to_flat_format(
398
+ gllm_mapping: dict[str, dict[str, str]],
399
+ ) -> dict[str, str]:
400
+ """Convert GLLM Privacy nested format back to flat format.
401
+
402
+ Inverse of _convert_flat_to_gllm_format. Flattens nested structure
403
+ {'PERSON': {'<PERSON_1>': 'Alice'}} back to {'<PERSON_1>': 'Alice'}.
404
+
405
+ Args:
406
+ gllm_mapping: Nested mapping from GLLM Privacy
407
+
408
+ Returns:
409
+ Flat mapping (tag → value)
410
+ """
411
+ flat_mapping: dict[str, str] = {}
412
+
413
+ for entity_type, tags_dict in gllm_mapping.items():
414
+ for tag, value in tags_dict.items():
415
+ flat_mapping[tag] = value
416
+
417
+ return flat_mapping
418
+
419
+ def _deanonymize_text(self, text: str) -> str:
420
+ """Deanonymize a single text string by replacing PII tags with real values.
421
+
422
+ Uses GLLM Privacy's TextAnonymizer.deanonymize() when NER is enabled,
423
+ otherwise falls back to simple string replacement. GLLM Privacy's deanonymize()
424
+ doesn't require entities parameter as they're configured during TextAnalyzer init.
425
+
426
+ Args:
427
+ text: Text containing PII tags (e.g., '<EMAIL_1>', '<PERSON_1>')
428
+
429
+ Returns:
430
+ Text with PII tags replaced by their real values from flat_pii_mapping
431
+ """
432
+ if self.enable_ner and self.text_anonymizer:
433
+ try:
434
+ restored_text = self.text_anonymizer.deanonymize(text=text)
435
+ return restored_text
436
+ except Exception as e:
437
+ logger.warning(f"GLLM Privacy deanonymization failed: {e}, falling back to simple replacement")
438
+ return self._replace_tags_in_text(text)
439
+ else:
440
+ return self._replace_tags_in_text(text)
441
+
442
+ def _anonymize_text(self, text: str) -> tuple[str, dict[str, str]]:
443
+ """Anonymize PII in text using a two-phase approach.
444
+
445
+ Phase 1: Masks known PII values using existing flat_pii_mapping (simple replacement).
446
+ Phase 2: Uses NER (if enabled) to detect and mask NEW PII values not in mapping.
447
+ Phase 3: Extracts newly discovered PII from GLLM Privacy's internal state.
448
+ Phase 4-5: Converts and merges new mappings into flat_pii_mapping.
449
+
450
+ Args:
451
+ text: Text that may contain real PII values to be masked
452
+
453
+ Returns:
454
+ Tuple of (anonymized_text, updated_flat_pii_mapping including new discoveries)
455
+ """
456
+ anonymized = self._mask_with_existing_mapping(text)
457
+
458
+ if self.enable_ner and self.text_anonymizer:
459
+ try:
460
+ result = self.text_anonymizer.anonymize(text=anonymized, entities=DEFAULT_SUPPORTED_ENTITIES)
461
+ if hasattr(result, "text"):
462
+ anonymized = result.text
463
+ else:
464
+ anonymized = result
465
+
466
+ gllm_mapping = self.text_anonymizer.deanonymizer_mapping
467
+ if gllm_mapping:
468
+ new_flat_mapping = self._convert_gllm_to_flat_format(gllm_mapping)
469
+ self.flat_pii_mapping.update(new_flat_mapping)
470
+ except Exception as e:
471
+ logger.warning(f"GLLM Privacy anonymization failed: {e}, continuing with masked text")
472
+
473
+ return anonymized, self.flat_pii_mapping
474
+
475
+ def _anonymize_dict(self, data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, str]]:
476
+ """Anonymize PII in dictionary recursively.
477
+
478
+ Processes string values with _anonymize_text(), recursively handles nested dicts,
479
+ and processes list items. Non-string/dict/list values are preserved as-is.
480
+
481
+ Args:
482
+ data: Dictionary that may contain PII values
483
+
484
+ Returns:
485
+ Tuple of (anonymized_dict, updated_mapping)
486
+ """
487
+ anonymized = {}
488
+
489
+ for key, value in data.items():
490
+ if isinstance(value, str):
491
+ anonymized_value, _ = self._anonymize_text(value)
492
+ anonymized[key] = anonymized_value
493
+ elif isinstance(value, dict):
494
+ anonymized[key], _ = self._anonymize_dict(value)
495
+ elif isinstance(value, list):
496
+ anonymized[key] = [self._anonymize_text(item)[0] if isinstance(item, str) else item for item in value]
497
+ else:
498
+ anonymized[key] = value
499
+
500
+ return anonymized, self.flat_pii_mapping
501
+
502
+ def _mask_with_existing_mapping(self, text: str) -> str:
503
+ """Mask PII using existing flat mapping.
504
+
505
+ Iterates through mapping in reverse order of value length to handle overlapping
506
+ PII values correctly (longer values are replaced first).
507
+
508
+ Args:
509
+ text: Text to mask
510
+
511
+ Returns:
512
+ Text with known PII values replaced by tags
513
+ """
514
+ masked = text
515
+ for tag, value in sorted(self.flat_pii_mapping.items(), key=lambda x: len(x[1]), reverse=True):
516
+ if value in masked:
517
+ masked = masked.replace(value, tag)
518
+ return masked
519
+
520
+ def _replace_tags_in_text(self, text: str) -> str:
521
+ """Replace PII tags with real values in text using flat mapping.
522
+
523
+ Args:
524
+ text: Text containing PII tags
525
+
526
+ Returns:
527
+ Text with tags replaced by values
528
+ """
529
+ replaced = text
530
+ for tag, value in self.flat_pii_mapping.items():
531
+ if tag in replaced:
532
+ replaced = replaced.replace(tag, value)
533
+ return replaced
534
+
535
+ def _process_value(self, value: Any, operation: Operation) -> Any:
536
+ """Process a value recursively based on operation type.
537
+
538
+ Args:
539
+ value: Value to process
540
+ operation: Operation enum value (ANONYMIZE or DEANONYMIZE)
541
+
542
+ Returns:
543
+ Processed value
544
+ """
545
+ if isinstance(value, str):
546
+ if operation == Operation.DEANONYMIZE:
547
+ return self._deanonymize_text(value)
548
+ else:
549
+ return self._anonymize_text(value)[0]
550
+ elif isinstance(value, dict):
551
+ return {k: self._process_value(v, operation) for k, v in value.items()}
552
+ elif isinstance(value, list):
553
+ return [self._process_value(item, operation) for item in value]
554
+ else:
555
+ return value