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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- 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)
|