PraisonAI 3.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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,498 @@
1
+ """
2
+ Tool Override Loader for PraisonAI.
3
+
4
+ Allows loading custom tools from files, modules, and directories.
5
+ Supports runtime tool registration with context manager pattern.
6
+ """
7
+
8
+ import ast
9
+ import importlib.util
10
+ import sys
11
+ from contextlib import contextmanager
12
+ from pathlib import Path
13
+ from typing import Any, Callable, Dict, Generator, List, Optional
14
+
15
+
16
+ class SecurityError(Exception):
17
+ """Raised when a security violation is detected."""
18
+ pass
19
+
20
+
21
+ class ToolOverrideLoader:
22
+ """
23
+ Loads custom tools from various sources.
24
+
25
+ Supports:
26
+ - Python file paths
27
+ - Module import paths
28
+ - Directories containing tool files
29
+
30
+ Security:
31
+ - Only local paths allowed by default
32
+ - Remote URLs rejected
33
+ - No auto-execution of arbitrary code
34
+ """
35
+
36
+ # Default custom tool directories
37
+ DEFAULT_TOOL_DIRS = [
38
+ "~/.praison/tools",
39
+ "~/.config/praison/tools",
40
+ ]
41
+
42
+ def __init__(self):
43
+ """Initialize tool override loader."""
44
+ self._loaded_tools: Dict[str, Callable] = {}
45
+
46
+ def get_default_tool_dirs(self) -> List[Path]:
47
+ """
48
+ Get default custom tool directories.
49
+
50
+ Returns:
51
+ List of Path objects for default tool directories
52
+ """
53
+ return [Path(d).expanduser() for d in self.DEFAULT_TOOL_DIRS]
54
+
55
+ def load_from_file(self, file_path: str) -> Dict[str, Callable]:
56
+ """
57
+ Load tools from a Python file.
58
+
59
+ Args:
60
+ file_path: Path to Python file containing tool functions
61
+
62
+ Returns:
63
+ Dict mapping tool names to callable functions
64
+
65
+ Raises:
66
+ SecurityError: If path is a remote URL
67
+ FileNotFoundError: If file doesn't exist
68
+ """
69
+ # Security check - reject remote URLs
70
+ if file_path.startswith(("http://", "https://", "ftp://")):
71
+ raise SecurityError(
72
+ f"Remote URLs not allowed for security: {file_path}. "
73
+ "Only local file paths are permitted."
74
+ )
75
+
76
+ path = Path(file_path).expanduser().resolve()
77
+
78
+ if not path.exists():
79
+ raise FileNotFoundError(f"Tool file not found: {path}")
80
+
81
+ if not path.suffix == ".py":
82
+ raise ValueError(f"Tool file must be a Python file (.py): {path}")
83
+
84
+ # Load the module
85
+ spec = importlib.util.spec_from_file_location(
86
+ f"custom_tools_{path.stem}",
87
+ path
88
+ )
89
+ if spec is None or spec.loader is None:
90
+ raise ImportError(f"Could not load module from: {path}")
91
+
92
+ module = importlib.util.module_from_spec(spec)
93
+ sys.modules[spec.name] = module
94
+ spec.loader.exec_module(module)
95
+
96
+ # Extract callable functions (tools)
97
+ tools = {}
98
+ for name in dir(module):
99
+ if name.startswith("_"):
100
+ continue
101
+ obj = getattr(module, name)
102
+ if callable(obj) and not isinstance(obj, type):
103
+ tools[name] = obj
104
+
105
+ self._loaded_tools.update(tools)
106
+ return tools
107
+
108
+ def load_from_module(self, module_path: str) -> Dict[str, Callable]:
109
+ """
110
+ Load tools from a module import path.
111
+
112
+ Args:
113
+ module_path: Python module path (e.g., 'mypackage.tools')
114
+
115
+ Returns:
116
+ Dict mapping tool names to callable functions
117
+ """
118
+ try:
119
+ module = importlib.import_module(module_path)
120
+ except ImportError as e:
121
+ raise ImportError(f"Could not import module: {module_path}") from e
122
+
123
+ # Extract callable functions (tools)
124
+ tools = {}
125
+ for name in dir(module):
126
+ if name.startswith("_"):
127
+ continue
128
+ obj = getattr(module, name)
129
+ if callable(obj) and not isinstance(obj, type):
130
+ tools[name] = obj
131
+
132
+ self._loaded_tools.update(tools)
133
+ return tools
134
+
135
+ def load_from_directory(self, dir_path: str) -> Dict[str, Callable]:
136
+ """
137
+ Load tools from all Python files in a directory.
138
+
139
+ Args:
140
+ dir_path: Path to directory containing tool files
141
+
142
+ Returns:
143
+ Dict mapping tool names to callable functions
144
+
145
+ Raises:
146
+ SecurityError: If path is a remote URL
147
+ """
148
+ # Security check
149
+ if dir_path.startswith(("http://", "https://", "ftp://")):
150
+ raise SecurityError(
151
+ f"Remote URLs not allowed for security: {dir_path}. "
152
+ "Only local directory paths are permitted."
153
+ )
154
+
155
+ path = Path(dir_path).expanduser().resolve()
156
+
157
+ if not path.exists():
158
+ raise FileNotFoundError(f"Tool directory not found: {path}")
159
+
160
+ if not path.is_dir():
161
+ raise ValueError(f"Path is not a directory: {path}")
162
+
163
+ tools = {}
164
+ for py_file in path.glob("*.py"):
165
+ if py_file.name.startswith("_"):
166
+ continue
167
+ try:
168
+ file_tools = self.load_from_file(str(py_file))
169
+ tools.update(file_tools)
170
+ except Exception:
171
+ # Skip files that fail to load
172
+ pass
173
+
174
+ return tools
175
+
176
+ def discover_tools_in_directory(self, dir_path: str) -> List[str]:
177
+ """
178
+ Discover tool names in a directory without executing code.
179
+
180
+ Uses AST parsing to find function definitions.
181
+
182
+ Args:
183
+ dir_path: Path to directory to scan
184
+
185
+ Returns:
186
+ List of discovered tool function names
187
+ """
188
+ path = Path(dir_path).expanduser().resolve()
189
+
190
+ if not path.exists() or not path.is_dir():
191
+ return []
192
+
193
+ tool_names = []
194
+ for py_file in path.glob("*.py"):
195
+ if py_file.name.startswith("_"):
196
+ continue
197
+ try:
198
+ content = py_file.read_text()
199
+ tree = ast.parse(content)
200
+
201
+ for node in ast.walk(tree):
202
+ if isinstance(node, ast.FunctionDef):
203
+ if not node.name.startswith("_"):
204
+ tool_names.append(node.name)
205
+ except Exception:
206
+ # Skip files that fail to parse
207
+ pass
208
+
209
+ return tool_names
210
+
211
+ @contextmanager
212
+ def override_context(
213
+ self,
214
+ files: Optional[List[str]] = None,
215
+ modules: Optional[List[str]] = None,
216
+ directories: Optional[List[str]] = None
217
+ ) -> Generator[Dict[str, Callable], None, None]:
218
+ """
219
+ Context manager for temporary tool overrides.
220
+
221
+ Tools loaded within this context are available only during
222
+ the context and are cleaned up afterward.
223
+
224
+ Args:
225
+ files: List of Python file paths to load
226
+ modules: List of module paths to import
227
+ directories: List of directories to scan
228
+
229
+ Yields:
230
+ Dict of loaded tools
231
+ """
232
+ # Store original state
233
+ original_tools = self._loaded_tools.copy()
234
+
235
+ try:
236
+ # Load tools from all sources
237
+ loaded = {}
238
+
239
+ if files:
240
+ for f in files:
241
+ loaded.update(self.load_from_file(f))
242
+
243
+ if modules:
244
+ for m in modules:
245
+ loaded.update(self.load_from_module(m))
246
+
247
+ if directories:
248
+ for d in directories:
249
+ loaded.update(self.load_from_directory(d))
250
+
251
+ yield loaded
252
+
253
+ finally:
254
+ # Restore original state
255
+ self._loaded_tools = original_tools
256
+
257
+ def get_loaded_tools(self) -> Dict[str, Callable]:
258
+ """
259
+ Get all currently loaded tools.
260
+
261
+ Returns:
262
+ Dict mapping tool names to callable functions
263
+ """
264
+ return self._loaded_tools.copy()
265
+
266
+ def clear_loaded_tools(self) -> None:
267
+ """Clear all loaded tools."""
268
+ self._loaded_tools.clear()
269
+
270
+
271
+ def create_tool_registry_with_overrides(
272
+ override_files: Optional[List[str]] = None,
273
+ override_dirs: Optional[List[str]] = None,
274
+ include_defaults: bool = True,
275
+ tools_sources: Optional[List[str]] = None,
276
+ template_dir: Optional[str] = None,
277
+ ) -> Dict[str, Callable]:
278
+ """
279
+ Create a tool registry with custom overrides.
280
+
281
+ Resolution order (highest priority first):
282
+ 1. Override files (explicit CLI --tools)
283
+ 2. Override directories (explicit CLI --tools-dir)
284
+ 3. Template tools_sources (from TEMPLATE.yaml)
285
+ 4. Template-local tools.py
286
+ 4.5. Current working directory tools.py (./tools.py)
287
+ 5. Default custom dirs (~/.praison/tools, etc.)
288
+ 6. Package discovery (praisonai-tools if installed)
289
+ 7. Built-in tools
290
+
291
+ Args:
292
+ override_files: Explicit tool files to load
293
+ override_dirs: Directories to scan for tools
294
+ include_defaults: Whether to include default tool directories
295
+ tools_sources: Template-declared tool sources (modules or paths)
296
+ template_dir: Template directory for local tools.py
297
+
298
+ Returns:
299
+ Dict mapping tool names to callable functions
300
+ """
301
+ registry = {}
302
+ loader = ToolOverrideLoader()
303
+
304
+ # 7. Start with built-in tools (lowest priority)
305
+ try:
306
+ from praisonaiagents.tools import TOOL_MAPPINGS
307
+ registry.update(TOOL_MAPPINGS)
308
+ except ImportError:
309
+ pass
310
+
311
+ # 6. Package discovery - try praisonai-tools if installed
312
+ try:
313
+ import praisonai_tools.tools as external_tools
314
+ # Get all exported tools from praisonai_tools
315
+ for name in dir(external_tools):
316
+ if not name.startswith('_'):
317
+ obj = getattr(external_tools, name, None)
318
+ if callable(obj) or (hasattr(obj, 'run') and callable(getattr(obj, 'run', None))):
319
+ registry[name] = obj
320
+ except ImportError:
321
+ pass
322
+
323
+ # 5. Add default custom dirs
324
+ if include_defaults:
325
+ for dir_path in loader.get_default_tool_dirs():
326
+ if dir_path.exists():
327
+ try:
328
+ tools = loader.load_from_directory(str(dir_path))
329
+ registry.update(tools)
330
+ except Exception:
331
+ pass
332
+
333
+ # 4.5. Current working directory tools.py (if exists)
334
+ cwd_tools_py = Path.cwd() / "tools.py"
335
+ if cwd_tools_py.exists():
336
+ try:
337
+ tools = loader.load_from_file(str(cwd_tools_py))
338
+ registry.update(tools)
339
+ except Exception:
340
+ pass
341
+
342
+ # 4. Template-local tools.py
343
+ if template_dir:
344
+ tools_py = Path(template_dir) / "tools.py"
345
+ if tools_py.exists():
346
+ try:
347
+ tools = loader.load_from_file(str(tools_py))
348
+ registry.update(tools)
349
+ except Exception:
350
+ pass
351
+
352
+ # 3. Template tools_sources (from TEMPLATE.yaml)
353
+ if tools_sources:
354
+ for source in tools_sources:
355
+ try:
356
+ # Security: only allow local paths and python modules
357
+ if source.startswith(("http://", "https://", "ftp://")):
358
+ continue # Skip remote URLs
359
+
360
+ source_path = Path(source).expanduser()
361
+
362
+ if source_path.exists():
363
+ # It's a local path
364
+ if source_path.is_file() and source_path.suffix == ".py":
365
+ tools = loader.load_from_file(str(source_path))
366
+ registry.update(tools)
367
+ elif source_path.is_dir():
368
+ tools = loader.load_from_directory(str(source_path))
369
+ registry.update(tools)
370
+ else:
371
+ # Try as a Python module path
372
+ tools = loader.load_from_module(source)
373
+ registry.update(tools)
374
+ except Exception:
375
+ pass
376
+
377
+ # 2. Add override directories (CLI --tools-dir)
378
+ if override_dirs:
379
+ for dir_path in override_dirs:
380
+ try:
381
+ tools = loader.load_from_directory(dir_path)
382
+ registry.update(tools)
383
+ except Exception:
384
+ pass
385
+
386
+ # 1. Add override files (highest priority, CLI --tools)
387
+ if override_files:
388
+ for file_path in override_files:
389
+ try:
390
+ tools = loader.load_from_file(file_path)
391
+ registry.update(tools)
392
+ except Exception:
393
+ pass
394
+
395
+ return registry
396
+
397
+
398
+ def resolve_tools(
399
+ tool_names: List[Any],
400
+ registry: Optional[Dict[str, Callable]] = None,
401
+ template_dir: Optional[str] = None,
402
+ ) -> List[Callable]:
403
+ """
404
+ Resolve tool names to callable tools from registry.
405
+
406
+ Handles:
407
+ - String tool names (looked up in registry)
408
+ - Already-callable tools (passed through)
409
+ - Built-in tool names (shell_tool, file_tool, etc.)
410
+
411
+ Args:
412
+ tool_names: List of tool names (strings) or callables
413
+ registry: Tool registry to look up names in
414
+ template_dir: Optional template directory for local tools.py autoload
415
+
416
+ Returns:
417
+ List of resolved callable tools
418
+ """
419
+ if not tool_names:
420
+ return []
421
+
422
+ resolved = []
423
+
424
+ # Build registry if not provided
425
+ if registry is None:
426
+ registry = create_tool_registry_with_overrides(include_defaults=True)
427
+
428
+ # Load template-local tools.py if exists
429
+ if template_dir:
430
+ loader = ToolOverrideLoader()
431
+ tools_py = Path(template_dir) / "tools.py"
432
+ if tools_py.exists():
433
+ try:
434
+ local_tools = loader.load_from_file(str(tools_py))
435
+ registry.update(local_tools)
436
+ except Exception:
437
+ pass
438
+
439
+ for tool in tool_names:
440
+ if callable(tool):
441
+ # Already a callable, use directly
442
+ resolved.append(tool)
443
+ elif isinstance(tool, str):
444
+ # Look up by name in registry
445
+ tool_name = tool.strip()
446
+
447
+ # Try exact match first
448
+ if tool_name in registry:
449
+ tool_obj = registry[tool_name]
450
+ # Handle lazy-loaded tools (tuples of module, class)
451
+ if isinstance(tool_obj, tuple):
452
+ try:
453
+ module_name, class_name = tool_obj
454
+ import importlib
455
+ module = importlib.import_module(module_name)
456
+ tool_class = getattr(module, class_name)
457
+ resolved.append(tool_class())
458
+ except Exception:
459
+ pass
460
+ elif callable(tool_obj):
461
+ resolved.append(tool_obj)
462
+ else:
463
+ # Try to instantiate if it's a class
464
+ try:
465
+ resolved.append(tool_obj())
466
+ except Exception:
467
+ resolved.append(tool_obj)
468
+ else:
469
+ # Try common variations
470
+ variations = [
471
+ tool_name,
472
+ tool_name.lower(),
473
+ tool_name.replace("-", "_"),
474
+ tool_name.replace("_", "-"),
475
+ f"{tool_name}_tool",
476
+ f"{tool_name}Tool",
477
+ ]
478
+ found = False
479
+ for var in variations:
480
+ if var in registry:
481
+ tool_obj = registry[var]
482
+ if callable(tool_obj):
483
+ resolved.append(tool_obj)
484
+ found = True
485
+ break
486
+
487
+ if not found:
488
+ # Try to import from praisonaiagents.tools
489
+ try:
490
+ from praisonaiagents import tools as agent_tools
491
+ if hasattr(agent_tools, tool_name):
492
+ resolved.append(getattr(agent_tools, tool_name))
493
+ elif hasattr(agent_tools, f"{tool_name}_tool"):
494
+ resolved.append(getattr(agent_tools, f"{tool_name}_tool"))
495
+ except (ImportError, AttributeError):
496
+ pass
497
+
498
+ return resolved