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,673 @@
1
+ """
2
+ n8n Integration Handler for PraisonAI CLI.
3
+
4
+ Converts PraisonAI agents.yaml workflows to n8n workflow JSON format
5
+ and opens them in the n8n UI.
6
+
7
+ Usage:
8
+ praisonai agents.yaml --n8n
9
+ praisonai agents.yaml --n8n --n8n-url http://localhost:5678
10
+ """
11
+
12
+ import json
13
+ import yaml
14
+ import webbrowser
15
+ import hashlib
16
+ import os
17
+ from typing import Any, Dict, List, Optional
18
+ from pathlib import Path
19
+
20
+ try:
21
+ import requests
22
+ REQUESTS_AVAILABLE = True
23
+ except ImportError:
24
+ REQUESTS_AVAILABLE = False
25
+
26
+ from .base import FlagHandler
27
+
28
+
29
+ class N8nHandler(FlagHandler):
30
+ """
31
+ Handler for n8n workflow export and visualization.
32
+
33
+ Converts PraisonAI YAML workflows to n8n-compatible JSON format
34
+ and opens them in the n8n UI for visual editing.
35
+ """
36
+
37
+ def __init__(self, verbose: bool = False, n8n_url: str = "http://localhost:5678",
38
+ use_execute_command: bool = False, api_url: str = "http://127.0.0.1:8005"):
39
+ """
40
+ Initialize the n8n handler.
41
+
42
+ Args:
43
+ verbose: Enable verbose output
44
+ n8n_url: Base URL of the n8n instance
45
+ use_execute_command: Use Execute Command nodes (runs praisonai directly)
46
+ instead of HTTP Request nodes
47
+ api_url: PraisonAI API URL that n8n will call (for tunnel/cloud deployments)
48
+ """
49
+ super().__init__(verbose=verbose)
50
+ self.n8n_url = n8n_url
51
+ self.praisonai_api_url = api_url
52
+ self.use_execute_command = use_execute_command
53
+
54
+ @property
55
+ def feature_name(self) -> str:
56
+ return "n8n"
57
+
58
+ @property
59
+ def flag_name(self) -> str:
60
+ return "n8n"
61
+
62
+ @property
63
+ def flag_help(self) -> str:
64
+ return "Export workflow to n8n and open in browser"
65
+
66
+ def execute(self, yaml_path: str, n8n_url: Optional[str] = None,
67
+ open_browser: bool = True, **kwargs) -> Dict[str, Any]:
68
+ """
69
+ Execute the n8n export workflow.
70
+
71
+ Args:
72
+ yaml_path: Path to the agents.yaml file
73
+ n8n_url: Optional custom n8n URL
74
+ open_browser: Whether to open the workflow in browser
75
+
76
+ Returns:
77
+ Dictionary with workflow JSON and URL
78
+ """
79
+ if n8n_url:
80
+ self.n8n_url = n8n_url
81
+
82
+ # Convert YAML to n8n workflow JSON
83
+ workflow_json = self.convert_yaml_to_n8n(yaml_path)
84
+
85
+ # Save workflow JSON to file
86
+ output_path = self._save_workflow_json(workflow_json, yaml_path)
87
+
88
+ self.print_status("✅ Workflow converted successfully!", "success")
89
+ self.print_status(f"📄 JSON saved to: {output_path}", "info")
90
+
91
+ # Try to create workflow via n8n API
92
+ api_key = os.environ.get('N8N_API_KEY')
93
+ workflow_id = None
94
+
95
+ if api_key and REQUESTS_AVAILABLE:
96
+ workflow_id = self._create_workflow_via_api(workflow_json, api_key)
97
+
98
+ webhook_url = None
99
+ if workflow_id:
100
+ # Successfully created via API - open directly
101
+ url = f"{self.n8n_url}/workflow/{workflow_id}"
102
+ self.print_status("🚀 Workflow created in n8n!", "success")
103
+
104
+ # Activate the workflow so webhook is available
105
+ if self._activate_workflow(workflow_id, api_key):
106
+ self.print_status("✅ Workflow activated!", "success")
107
+
108
+ # Get webhook path from the workflow
109
+ trigger_node = next((n for n in workflow_json["nodes"]
110
+ if n.get("type") == "n8n-nodes-base.webhook"), None)
111
+ if trigger_node:
112
+ webhook_path = trigger_node["parameters"].get("path", "praisonai")
113
+ webhook_url = f"{self.n8n_url}/webhook/{webhook_path}"
114
+ self.print_status("", "info")
115
+ self.print_status("🔗 Webhook URL (to trigger workflow):", "info")
116
+ self.print_status(f" POST {webhook_url}", "info")
117
+ else:
118
+ self.print_status("⚠️ Could not activate workflow (activate manually in n8n)", "warning")
119
+
120
+ if open_browser:
121
+ self.open_in_browser(url)
122
+ self.print_status(f"🌐 Opening: {url}", "info")
123
+ else:
124
+ # Fallback to manual import instructions
125
+ url = self.generate_n8n_url(workflow_json, self.n8n_url)
126
+
127
+ if not api_key:
128
+ self.print_status("", "info")
129
+ self.print_status("💡 Tip: Set N8N_API_KEY env var for auto-import", "info")
130
+ self.print_status(" Generate API key in n8n: Settings → API", "info")
131
+
132
+ self.print_status("", "info")
133
+ self.print_status("📋 To import into n8n:", "info")
134
+ self.print_status(" 1. Open n8n in your browser", "info")
135
+ self.print_status(" 2. Click the three dots menu (⋮) in the top right", "info")
136
+ self.print_status(" 3. Select 'Import from File'", "info")
137
+ self.print_status(f" 4. Choose: {output_path}", "info")
138
+
139
+ if open_browser:
140
+ self.open_in_browser(url)
141
+ self.print_status(f"🌐 Opening n8n: {url}", "info")
142
+
143
+ return {
144
+ "workflow": workflow_json,
145
+ "url": url,
146
+ "output_path": output_path,
147
+ "workflow_id": workflow_id,
148
+ "webhook_url": webhook_url
149
+ }
150
+
151
+ def _create_workflow_via_api(self, workflow_json: Dict[str, Any],
152
+ api_key: str) -> Optional[str]:
153
+ """
154
+ Create workflow in n8n via REST API.
155
+
156
+ Args:
157
+ workflow_json: The n8n workflow JSON
158
+ api_key: n8n API key
159
+
160
+ Returns:
161
+ Workflow ID if successful, None otherwise
162
+ """
163
+ if not REQUESTS_AVAILABLE:
164
+ return None
165
+
166
+ try:
167
+ # Only include fields allowed by n8n API schema
168
+ # See: n8n/packages/cli/src/public-api/v1/handlers/workflows/spec/schemas/workflow.yml
169
+ workflow_data = {
170
+ "name": workflow_json.get("name", "PraisonAI Workflow"),
171
+ "nodes": workflow_json.get("nodes", []),
172
+ "connections": workflow_json.get("connections", {}),
173
+ "settings": workflow_json.get("settings", {"executionOrder": "v1"}),
174
+ }
175
+
176
+ # Optional fields
177
+ if "staticData" in workflow_json:
178
+ workflow_data["staticData"] = workflow_json["staticData"]
179
+
180
+ response = requests.post(
181
+ f"{self.n8n_url}/api/v1/workflows",
182
+ headers={
183
+ "X-N8N-API-KEY": api_key,
184
+ "Content-Type": "application/json"
185
+ },
186
+ json=workflow_data,
187
+ timeout=30
188
+ )
189
+
190
+ if response.status_code == 200:
191
+ result = response.json()
192
+ return result.get('id')
193
+ else:
194
+ self.log(f"API error: {response.status_code} - {response.text}", "warning")
195
+ return None
196
+
197
+ except Exception as e:
198
+ self.log(f"Failed to create workflow via API: {e}", "warning")
199
+ return None
200
+
201
+ def _activate_workflow(self, workflow_id: str, api_key: str) -> bool:
202
+ """
203
+ Activate a workflow via n8n API.
204
+
205
+ Args:
206
+ workflow_id: The workflow ID to activate
207
+ api_key: n8n API key
208
+
209
+ Returns:
210
+ True if successful, False otherwise
211
+ """
212
+ if not REQUESTS_AVAILABLE:
213
+ return False
214
+
215
+ try:
216
+ response = requests.post(
217
+ f"{self.n8n_url}/api/v1/workflows/{workflow_id}/activate",
218
+ headers={
219
+ "X-N8N-API-KEY": api_key,
220
+ "Content-Type": "application/json"
221
+ },
222
+ timeout=30
223
+ )
224
+
225
+ if response.status_code == 200:
226
+ return True
227
+ else:
228
+ self.log(f"Activation error: {response.status_code} - {response.text}", "warning")
229
+ return False
230
+
231
+ except Exception as e:
232
+ self.log(f"Failed to activate workflow: {e}", "warning")
233
+ return False
234
+
235
+ def convert_yaml_to_n8n(self, yaml_path: str, use_webhook: bool = True) -> Dict[str, Any]:
236
+ """
237
+ Convert a PraisonAI YAML workflow to n8n JSON format.
238
+
239
+ The n8n workflow will have:
240
+ - A webhook trigger to receive requests
241
+ - One HTTP Request node per agent (calls /agents/{agent_name})
242
+ - Each agent node passes its output to the next agent
243
+
244
+ Args:
245
+ yaml_path: Path to the agents.yaml file
246
+ use_webhook: Use webhook trigger for programmatic execution (default: True)
247
+
248
+ Returns:
249
+ n8n workflow JSON as dictionary
250
+ """
251
+ # Load YAML file
252
+ with open(yaml_path, 'r') as f:
253
+ yaml_content = yaml.safe_load(f)
254
+
255
+ # Extract workflow metadata
256
+ workflow_name = yaml_content.get('name', 'PraisonAI Workflow')
257
+ description = yaml_content.get('description', '')
258
+
259
+ # Generate a unique webhook path from workflow name
260
+ webhook_path = workflow_name.lower().replace(' ', '-').replace('/', '-')[:50]
261
+
262
+ # Get agents and steps
263
+ agents = yaml_content.get('agents', yaml_content.get('roles', {}))
264
+ steps = yaml_content.get('steps', [])
265
+
266
+ # If no steps defined, create steps from agents
267
+ if not steps and agents:
268
+ steps = self._create_steps_from_agents(agents)
269
+
270
+ # Build n8n nodes and connections
271
+ nodes = []
272
+ connections = {}
273
+
274
+ # Add trigger node (webhook for programmatic execution, manual otherwise)
275
+ trigger_node = self._create_trigger_node(use_webhook=use_webhook, webhook_path=webhook_path)
276
+ nodes.append(trigger_node)
277
+
278
+ # Track previous node for connections
279
+ prev_node_name = trigger_node["name"]
280
+ x_position = 450
281
+
282
+ # Create one HTTP node per agent step
283
+ for i, step in enumerate(steps):
284
+ if isinstance(step, dict):
285
+ agent_id = step.get('agent', '')
286
+ action = step.get('action', step.get('description', ''))
287
+ agent_config = agents.get(agent_id, {})
288
+
289
+ # Create HTTP node that calls the specific agent endpoint
290
+ agent_node = self._create_per_agent_node(
291
+ agent_id=agent_id,
292
+ agent_config=agent_config,
293
+ action=action,
294
+ position=[x_position, 300],
295
+ index=i,
296
+ is_first=(i == 0)
297
+ )
298
+ nodes.append(agent_node)
299
+
300
+ # Connect to previous node
301
+ if prev_node_name not in connections:
302
+ connections[prev_node_name] = {"main": [[]]}
303
+ connections[prev_node_name]["main"][0].append({
304
+ "node": agent_node["name"],
305
+ "type": "main",
306
+ "index": 0
307
+ })
308
+
309
+ prev_node_name = agent_node["name"]
310
+ x_position += 250
311
+
312
+ # Build the complete workflow
313
+ # Generate a unique ID based on workflow name
314
+ workflow_id = hashlib.md5(workflow_name.encode()).hexdigest()[:8]
315
+
316
+ workflow = {
317
+ "id": workflow_id,
318
+ "name": workflow_name,
319
+ "nodes": nodes,
320
+ "connections": connections,
321
+ "active": False,
322
+ "settings": {
323
+ "executionOrder": "v1"
324
+ },
325
+ "versionId": "1",
326
+ "pinData": {},
327
+ "meta": {
328
+ "instanceId": "praisonai-export",
329
+ "templateCredsSetupCompleted": True
330
+ },
331
+ "tags": []
332
+ }
333
+
334
+ if description:
335
+ workflow["meta"]["description"] = description
336
+
337
+ return workflow
338
+
339
+ def _create_steps_from_agents(self, agents: Dict[str, Any]) -> List[Dict]:
340
+ """Create steps from agents when no steps are defined."""
341
+ steps = []
342
+ for agent_id, agent_config in agents.items():
343
+ if isinstance(agent_config, dict):
344
+ # Check for tasks nested in agent
345
+ tasks = agent_config.get('tasks', {})
346
+ if tasks:
347
+ for task_id, task_config in tasks.items():
348
+ steps.append({
349
+ 'agent': agent_id,
350
+ 'action': task_config.get('description', task_config.get('action', f'Execute {task_id}'))
351
+ })
352
+ else:
353
+ # Create a default step for the agent
354
+ steps.append({
355
+ 'agent': agent_id,
356
+ 'action': agent_config.get('instructions', agent_config.get('goal', f'Execute {agent_id}'))
357
+ })
358
+ return steps
359
+
360
+ def _create_trigger_node(self, use_webhook: bool = False,
361
+ webhook_path: str = "praisonai") -> Dict[str, Any]:
362
+ """Create a trigger node (webhook or manual).
363
+
364
+ Args:
365
+ use_webhook: If True, creates a webhook trigger for programmatic execution
366
+ webhook_path: Path for the webhook URL (only used if use_webhook=True)
367
+ """
368
+ if use_webhook:
369
+ # Generate a unique webhook ID
370
+ webhook_id = hashlib.md5(webhook_path.encode()).hexdigest()[:16]
371
+ return {
372
+ "id": "trigger",
373
+ "name": "Webhook",
374
+ "type": "n8n-nodes-base.webhook",
375
+ "typeVersion": 2,
376
+ "position": [250, 300],
377
+ "webhookId": webhook_id,
378
+ "parameters": {
379
+ "path": webhook_path,
380
+ "httpMethod": "POST",
381
+ "responseMode": "lastNode",
382
+ "options": {}
383
+ }
384
+ }
385
+ return {
386
+ "id": "trigger",
387
+ "name": "Manual Trigger",
388
+ "type": "n8n-nodes-base.manualTrigger",
389
+ "typeVersion": 1,
390
+ "position": [250, 300],
391
+ "parameters": {}
392
+ }
393
+
394
+ def _create_per_agent_node(self, agent_id: str, agent_config: Dict,
395
+ action: str, position: List[int],
396
+ index: int, is_first: bool = False) -> Dict[str, Any]:
397
+ """
398
+ Create an HTTP Request node that calls a specific agent endpoint.
399
+
400
+ Args:
401
+ agent_id: Agent identifier (used in URL path)
402
+ agent_config: Agent configuration from YAML
403
+ action: The action/task description for this agent
404
+ position: Node position [x, y]
405
+ index: Node index
406
+ is_first: Whether this is the first agent (uses webhook input)
407
+
408
+ Returns:
409
+ n8n HTTP Request node configuration
410
+ """
411
+ agent_name = agent_config.get('name', agent_id.title())
412
+ # Convert agent_id to URL-safe format (lowercase, underscores)
413
+ agent_url_id = agent_id.lower().replace(' ', '_')
414
+
415
+ # Build the query - first agent uses webhook input, others use previous response
416
+ # Escape single quotes and double braces (n8n expression syntax)
417
+ escaped_action = action.replace("'", "\\'").replace("{{", "").replace("}}", "")
418
+ if is_first:
419
+ # First agent: use query from webhook body
420
+ query_expr = "$json.body?.query || $json.query || '" + escaped_action + "'"
421
+ else:
422
+ # Subsequent agents: use previous agent's response + original action context
423
+ query_expr = "$json.response || '" + escaped_action + "'"
424
+
425
+ return {
426
+ "id": f"agent_{index}",
427
+ "name": agent_name,
428
+ "type": "n8n-nodes-base.httpRequest",
429
+ "typeVersion": 4.2,
430
+ "position": position,
431
+ "parameters": {
432
+ "method": "POST",
433
+ "url": f"{self.praisonai_api_url}/agents/{agent_url_id}",
434
+ "sendBody": True,
435
+ "specifyBody": "json",
436
+ "jsonBody": f"={{{{ JSON.stringify({{ query: {query_expr} }}) }}}}",
437
+ "options": {
438
+ "timeout": 300000 # 5 minute timeout per agent
439
+ }
440
+ },
441
+ "notes": f"Agent: {agent_name}\nTask: {action[:100]}"
442
+ }
443
+
444
+ def _create_praisonai_workflow_node(self, workflow_name: str,
445
+ agent_names: List[str],
446
+ position: List[int]) -> Dict[str, Any]:
447
+ """
448
+ Create a single HTTP Request node that triggers the entire PraisonAI workflow.
449
+
450
+ The PraisonAI API will run all agents sequentially when called.
451
+
452
+ Args:
453
+ workflow_name: Name of the workflow
454
+ agent_names: List of agent names (for documentation)
455
+ position: Node position [x, y]
456
+
457
+ Returns:
458
+ n8n HTTP Request node configuration
459
+ """
460
+ # The query will be passed from the webhook trigger
461
+ # Using n8n expression to get the body from the webhook
462
+ return {
463
+ "id": "praisonai_workflow",
464
+ "name": f"PraisonAI: {workflow_name}",
465
+ "type": "n8n-nodes-base.httpRequest",
466
+ "typeVersion": 4.2,
467
+ "position": position,
468
+ "parameters": {
469
+ "method": "POST",
470
+ "url": f"{self.praisonai_api_url}/agents",
471
+ "sendBody": True,
472
+ "specifyBody": "json",
473
+ # Pass the query from webhook body, or use a default
474
+ "jsonBody": "={{ JSON.stringify({ query: $json.body?.query || $json.query || 'Start workflow' }) }}",
475
+ "options": {
476
+ "timeout": 600000 # 10 minute timeout for full workflow
477
+ }
478
+ },
479
+ "notes": f"Runs agents: {', '.join(agent_names)}"
480
+ }
481
+
482
+ def _create_agent_node(self, agent_id: str, agent_config: Dict,
483
+ action: str, position: List[int],
484
+ index: int) -> Dict[str, Any]:
485
+ """Create a node for an agent - either Execute Command or HTTP Request."""
486
+ if self.use_execute_command:
487
+ return self._create_execute_command_node(
488
+ agent_id, agent_config, action, position, index
489
+ )
490
+ else:
491
+ return self._create_http_request_node(
492
+ agent_id, agent_config, action, position, index
493
+ )
494
+
495
+ def _create_execute_command_node(self, agent_id: str, agent_config: Dict,
496
+ action: str, position: List[int],
497
+ index: int) -> Dict[str, Any]:
498
+ """Create an Execute Command node that runs praisonai directly."""
499
+ agent_name = agent_config.get('name', agent_id.title())
500
+
501
+ # Escape quotes in the action for shell command
502
+ escaped_action = action.replace('"', '\\"').replace("'", "\\'")
503
+
504
+ # Build the praisonai command
505
+ # Uses the input from previous node if available
506
+ command = f'praisonai "{escaped_action}"'
507
+
508
+ return {
509
+ "id": f"agent_{index}",
510
+ "name": agent_name,
511
+ "type": "n8n-nodes-base.executeCommand",
512
+ "typeVersion": 1,
513
+ "position": position,
514
+ "parameters": {
515
+ "command": command,
516
+ "executeOnce": True
517
+ }
518
+ }
519
+
520
+ def _create_http_request_node(self, agent_id: str, agent_config: Dict,
521
+ action: str, position: List[int],
522
+ index: int) -> Dict[str, Any]:
523
+ """Create an HTTP Request node for an agent."""
524
+ agent_name = agent_config.get('name', agent_id.title())
525
+
526
+ # Build the request body
527
+ request_body = {
528
+ "query": action,
529
+ "agent": agent_id
530
+ }
531
+
532
+ return {
533
+ "id": f"agent_{index}",
534
+ "name": agent_name,
535
+ "type": "n8n-nodes-base.httpRequest",
536
+ "typeVersion": 4.2,
537
+ "position": position,
538
+ "parameters": {
539
+ "method": "POST",
540
+ "url": f"{self.praisonai_api_url}/agents",
541
+ "sendBody": True,
542
+ "specifyBody": "json",
543
+ "jsonBody": json.dumps(request_body),
544
+ "options": {
545
+ "timeout": 300000 # 5 minute timeout for LLM calls
546
+ }
547
+ }
548
+ }
549
+
550
+ def _create_parallel_nodes(self, parallel_steps: List[Dict],
551
+ x_position: int,
552
+ agents: Dict[str, Any]) -> tuple:
553
+ """Create nodes for parallel execution."""
554
+ nodes = []
555
+ connections = {}
556
+
557
+ for i, step in enumerate(parallel_steps):
558
+ agent_id = step.get('agent', '')
559
+ action = step.get('action', step.get('description', ''))
560
+ agent_config = agents.get(agent_id, {})
561
+
562
+ node = self._create_agent_node(
563
+ agent_id=agent_id,
564
+ agent_config=agent_config,
565
+ action=action,
566
+ position=[x_position + (i * 200), 300 + (i * 100)],
567
+ index=100 + i # Offset index for parallel nodes
568
+ )
569
+ nodes.append(node)
570
+
571
+ return nodes, connections
572
+
573
+ def _create_route_node(self, step: Dict, x_position: int,
574
+ index: int) -> Dict[str, Any]:
575
+ """Create an IF node for routing decisions."""
576
+ route_config = step.get('route', {})
577
+ conditions = list(route_config.keys())
578
+
579
+ return {
580
+ "id": f"route_{index}",
581
+ "name": f"Route Decision {index}",
582
+ "type": "n8n-nodes-base.if",
583
+ "typeVersion": 2,
584
+ "position": [x_position, 300],
585
+ "parameters": {
586
+ "conditions": {
587
+ "options": {
588
+ "caseSensitive": True,
589
+ "leftValue": "",
590
+ "typeValidation": "strict"
591
+ },
592
+ "conditions": [
593
+ {
594
+ "id": f"condition_{i}",
595
+ "leftValue": "={{ $json.result }}",
596
+ "rightValue": condition,
597
+ "operator": {
598
+ "type": "string",
599
+ "operation": "contains"
600
+ }
601
+ }
602
+ for i, condition in enumerate(conditions) if condition != 'default'
603
+ ]
604
+ }
605
+ }
606
+ }
607
+
608
+ def generate_n8n_url(self, workflow_json: Dict[str, Any],
609
+ base_url: str) -> str:
610
+ """
611
+ Generate a URL to open n8n workflow editor.
612
+
613
+ Note: n8n doesn't support URL hash-based import. Users need to:
614
+ 1. Open n8n
615
+ 2. Use "Import from File" to load the saved JSON
616
+
617
+ Args:
618
+ workflow_json: The n8n workflow JSON (unused, kept for API compatibility)
619
+ base_url: Base URL of the n8n instance
620
+
621
+ Returns:
622
+ URL to open n8n workflow editor
623
+ """
624
+ # n8n doesn't support URL-based workflow import via hash
625
+ # Return the new workflow URL - user will need to import manually
626
+ return f"{base_url}/workflow/new"
627
+
628
+ def open_in_browser(self, url: str) -> None:
629
+ """
630
+ Open a URL in the default web browser.
631
+
632
+ Args:
633
+ url: URL to open
634
+ """
635
+ try:
636
+ webbrowser.open(url)
637
+ except Exception as e:
638
+ self.print_status(f"Could not open browser: {e}", "warning")
639
+ self.print_status(f"Please open this URL manually: {url}", "info")
640
+
641
+ def _save_workflow_json(self, workflow_json: Dict[str, Any],
642
+ yaml_path: str) -> str:
643
+ """
644
+ Save the workflow JSON to a file.
645
+
646
+ Args:
647
+ workflow_json: The n8n workflow JSON
648
+ yaml_path: Original YAML file path
649
+
650
+ Returns:
651
+ Path to the saved JSON file
652
+ """
653
+ # Create output path based on input YAML
654
+ yaml_file = Path(yaml_path)
655
+ output_path = yaml_file.parent / f"{yaml_file.stem}_n8n.json"
656
+
657
+ with open(output_path, 'w') as f:
658
+ json.dump(workflow_json, f, indent=2)
659
+
660
+ return str(output_path)
661
+
662
+ def get_workflow_json_for_clipboard(self, yaml_path: str) -> str:
663
+ """
664
+ Get the workflow JSON as a string for clipboard copy.
665
+
666
+ Args:
667
+ yaml_path: Path to the agents.yaml file
668
+
669
+ Returns:
670
+ JSON string ready for clipboard
671
+ """
672
+ workflow_json = self.convert_yaml_to_n8n(yaml_path)
673
+ return json.dumps(workflow_json, indent=2)