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
praisonai/deploy.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
|
|
6
|
+
class CloudDeployer:
|
|
7
|
+
"""
|
|
8
|
+
A class for deploying a cloud-based application.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
None
|
|
12
|
+
|
|
13
|
+
Methods:
|
|
14
|
+
__init__(self):
|
|
15
|
+
Loads environment variables from .env file or system and sets them.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""
|
|
20
|
+
Loads environment variables from .env file or system and sets them.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
self: An instance of the CloudDeployer class.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
None
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
None
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
# Load environment variables from .env file or system
|
|
33
|
+
load_dotenv()
|
|
34
|
+
self.set_environment_variables()
|
|
35
|
+
|
|
36
|
+
def create_dockerfile(self):
|
|
37
|
+
"""
|
|
38
|
+
Creates a Dockerfile for the application.
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
self: An instance of the CloudDeployer class.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
None
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
None
|
|
48
|
+
|
|
49
|
+
This method creates a Dockerfile in the current directory with the specified content.
|
|
50
|
+
The Dockerfile is used to build a Docker image for the application.
|
|
51
|
+
The content of the Dockerfile includes instructions to use the Python 3.11-slim base image,
|
|
52
|
+
set the working directory to /app, copy the current directory contents into the container,
|
|
53
|
+
install the required Python packages (flask, praisonai, gunicorn, and markdown),
|
|
54
|
+
expose port 8080, and run the application using Gunicorn.
|
|
55
|
+
"""
|
|
56
|
+
with open("Dockerfile", "w") as file:
|
|
57
|
+
file.write("FROM python:3.11-slim\n")
|
|
58
|
+
file.write("WORKDIR /app\n")
|
|
59
|
+
file.write("COPY . .\n")
|
|
60
|
+
file.write("RUN pip install flask praisonai==3.0.0 gunicorn markdown\n")
|
|
61
|
+
file.write("EXPOSE 8080\n")
|
|
62
|
+
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
|
63
|
+
|
|
64
|
+
def create_api_file(self):
|
|
65
|
+
"""
|
|
66
|
+
Creates an API file for the application.
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
self (CloudDeployer): An instance of the CloudDeployer class.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
None
|
|
73
|
+
|
|
74
|
+
This method creates an API file named "api.py" in the current directory. The file contains a basic Flask application that uses the PraisonAI library to run a simple agent and returns the output as an HTML page. The application listens on the root path ("/") and uses the Markdown library to format the output.
|
|
75
|
+
"""
|
|
76
|
+
with open("api.py", "w") as file:
|
|
77
|
+
file.write("from flask import Flask\n")
|
|
78
|
+
file.write("from praisonai import PraisonAI\n")
|
|
79
|
+
file.write("import markdown\n\n")
|
|
80
|
+
file.write("app = Flask(__name__)\n\n")
|
|
81
|
+
file.write("def basic():\n")
|
|
82
|
+
file.write(" praisonai = PraisonAI(agent_file=\"agents.yaml\")\n")
|
|
83
|
+
file.write(" return praisonai.run()\n\n")
|
|
84
|
+
file.write("@app.route('/')\n")
|
|
85
|
+
file.write("def home():\n")
|
|
86
|
+
file.write(" output = basic()\n")
|
|
87
|
+
file.write(" html_output = markdown.markdown(output)\n")
|
|
88
|
+
file.write(" return f'<html><body>{html_output}</body></html>'\n\n")
|
|
89
|
+
file.write("if __name__ == \"__main__\":\n")
|
|
90
|
+
file.write(" app.run(debug=True)\n")
|
|
91
|
+
|
|
92
|
+
def set_environment_variables(self):
|
|
93
|
+
"""Sets environment variables with fallback to .env values or defaults."""
|
|
94
|
+
os.environ["OPENAI_MODEL_NAME"] = os.getenv("OPENAI_MODEL_NAME", "gpt-5-nano")
|
|
95
|
+
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "Enter your API key")
|
|
96
|
+
os.environ["OPENAI_API_BASE"] = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
|
|
97
|
+
|
|
98
|
+
def run_commands(self):
|
|
99
|
+
"""
|
|
100
|
+
Sets environment variables with fallback to .env values or defaults.
|
|
101
|
+
|
|
102
|
+
Parameters:
|
|
103
|
+
None
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
None
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
None
|
|
110
|
+
|
|
111
|
+
This method sets environment variables for the application. It uses the `os.environ` dictionary to set the following environment variables:
|
|
112
|
+
|
|
113
|
+
- `OPENAI_MODEL_NAME`: The name of the OpenAI model to use. If not specified in the .env file, it defaults to "gpt-5-nano".
|
|
114
|
+
- `OPENAI_API_KEY`: The API key for accessing the OpenAI API. If not specified in the .env file, it defaults to "Enter your API key".
|
|
115
|
+
- `OPENAI_API_BASE`: The base URL for the OpenAI API. If not specified in the .env file, it defaults to "https://api.openai.com/v1".
|
|
116
|
+
"""
|
|
117
|
+
self.create_api_file()
|
|
118
|
+
self.create_dockerfile()
|
|
119
|
+
"""Runs a sequence of shell commands for deployment, continues on error."""
|
|
120
|
+
|
|
121
|
+
# Get project ID upfront for Windows compatibility
|
|
122
|
+
try:
|
|
123
|
+
result = subprocess.run(['gcloud', 'config', 'get-value', 'project'],
|
|
124
|
+
capture_output=True, text=True, check=True)
|
|
125
|
+
project_id = result.stdout.strip()
|
|
126
|
+
except subprocess.CalledProcessError:
|
|
127
|
+
print("ERROR: Failed to get GCP project ID. Ensure gcloud is configured.")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Get environment variables
|
|
131
|
+
openai_model = os.environ.get('OPENAI_MODEL_NAME', 'gpt-5-nano')
|
|
132
|
+
openai_key = os.environ.get('OPENAI_API_KEY', 'Enter your API key')
|
|
133
|
+
openai_base = os.environ.get('OPENAI_API_BASE', 'https://api.openai.com/v1')
|
|
134
|
+
|
|
135
|
+
# Build commands with actual values
|
|
136
|
+
commands = [
|
|
137
|
+
['gcloud', 'auth', 'configure-docker', 'us-central1-docker.pkg.dev'],
|
|
138
|
+
['gcloud', 'artifacts', 'repositories', 'create', 'praisonai-repository',
|
|
139
|
+
'--repository-format=docker', '--location=us-central1'],
|
|
140
|
+
['docker', 'build', '--platform', 'linux/amd64', '-t',
|
|
141
|
+
f'gcr.io/{project_id}/praisonai-app:latest', '.'],
|
|
142
|
+
['docker', 'tag', f'gcr.io/{project_id}/praisonai-app:latest',
|
|
143
|
+
f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],
|
|
144
|
+
['docker', 'push',
|
|
145
|
+
f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],
|
|
146
|
+
['gcloud', 'run', 'deploy', 'praisonai-service',
|
|
147
|
+
'--image', f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest',
|
|
148
|
+
'--platform', 'managed', '--region', 'us-central1', '--allow-unauthenticated',
|
|
149
|
+
'--set-env-vars', f'OPENAI_MODEL_NAME={openai_model},OPENAI_API_KEY={openai_key},OPENAI_API_BASE={openai_base}']
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# Run commands with appropriate handling for each platform
|
|
153
|
+
for i, cmd in enumerate(commands):
|
|
154
|
+
try:
|
|
155
|
+
if i == 0: # First command (gcloud auth configure-docker)
|
|
156
|
+
if platform.system() != 'Windows':
|
|
157
|
+
# On Unix, pipe 'yes' to auto-confirm
|
|
158
|
+
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
|
|
159
|
+
proc.communicate(input=b'Y\n')
|
|
160
|
+
if proc.returncode != 0:
|
|
161
|
+
raise subprocess.CalledProcessError(proc.returncode, cmd)
|
|
162
|
+
else:
|
|
163
|
+
# On Windows, try with --quiet flag to avoid prompts
|
|
164
|
+
cmd_with_quiet = cmd + ['--quiet']
|
|
165
|
+
try:
|
|
166
|
+
subprocess.run(cmd_with_quiet, check=True)
|
|
167
|
+
except subprocess.CalledProcessError:
|
|
168
|
+
# If --quiet fails, try without it
|
|
169
|
+
print("Note: You may need to manually confirm the authentication prompt")
|
|
170
|
+
subprocess.run(cmd, check=True)
|
|
171
|
+
else:
|
|
172
|
+
# Run other commands normally
|
|
173
|
+
subprocess.run(cmd, check=True)
|
|
174
|
+
except subprocess.CalledProcessError as e:
|
|
175
|
+
print(f"ERROR: Command failed with exit status {e.returncode}")
|
|
176
|
+
# Commands 2 (build) and 4 (push) and 5 (deploy) are critical
|
|
177
|
+
if i in [2, 4, 5]:
|
|
178
|
+
print("Critical command failed. Aborting deployment.")
|
|
179
|
+
return
|
|
180
|
+
print(f"Continuing with the next command...")
|
|
181
|
+
|
|
182
|
+
# Usage
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
deployer = CloudDeployer()
|
|
185
|
+
deployer.run_commands()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Endpoints Module
|
|
3
|
+
|
|
4
|
+
Provides unified discovery, provider adapters, and server utilities
|
|
5
|
+
for all PraisonAI serve features.
|
|
6
|
+
|
|
7
|
+
Provider Types:
|
|
8
|
+
- recipe: Recipe runner endpoints
|
|
9
|
+
- agents-api: Single/multi-agent HTTP API
|
|
10
|
+
- mcp: MCP server (stdio, http, sse)
|
|
11
|
+
- tools-mcp: Tools exposed as MCP server
|
|
12
|
+
- a2a: Agent-to-agent protocol
|
|
13
|
+
- a2u: Agent-to-user event stream
|
|
14
|
+
|
|
15
|
+
Architecture:
|
|
16
|
+
- Discovery schema is versioned and consistent across all providers
|
|
17
|
+
- Provider adapters abstract protocol differences
|
|
18
|
+
- CLI client uses unified discovery for list/describe/invoke/health
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Lazy loading for all exports
|
|
22
|
+
_lazy_imports = {
|
|
23
|
+
"DiscoveryDocument": ".discovery",
|
|
24
|
+
"EndpointInfo": ".discovery",
|
|
25
|
+
"ProviderInfo": ".discovery",
|
|
26
|
+
"SCHEMA_VERSION": ".discovery",
|
|
27
|
+
"create_discovery_document": ".discovery",
|
|
28
|
+
"BaseProvider": ".providers.base",
|
|
29
|
+
"RecipeProvider": ".providers.recipe",
|
|
30
|
+
"AgentsAPIProvider": ".providers.agents_api",
|
|
31
|
+
"MCPProvider": ".providers.mcp",
|
|
32
|
+
"ToolsMCPProvider": ".providers.tools_mcp",
|
|
33
|
+
"A2AProvider": ".providers.a2a",
|
|
34
|
+
"A2UProvider": ".providers.a2u",
|
|
35
|
+
"ProviderRegistry": ".registry",
|
|
36
|
+
"get_provider": ".registry",
|
|
37
|
+
"register_provider": ".registry",
|
|
38
|
+
"list_provider_types": ".registry",
|
|
39
|
+
"get_provider_class": ".registry",
|
|
40
|
+
"create_unified_app": ".server",
|
|
41
|
+
"add_discovery_routes": ".server",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def __getattr__(name: str):
|
|
45
|
+
if name in _lazy_imports:
|
|
46
|
+
module_path = _lazy_imports[name]
|
|
47
|
+
import importlib
|
|
48
|
+
module = importlib.import_module(module_path, package=__name__)
|
|
49
|
+
return getattr(module, name)
|
|
50
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
51
|
+
|
|
52
|
+
def __dir__():
|
|
53
|
+
return list(_lazy_imports.keys())
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A2U (Agent-to-User) Event Stream Server
|
|
3
|
+
|
|
4
|
+
Provides SSE-based event streaming for agent-to-user communication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Dict, List, Optional, Set
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class A2UEvent:
|
|
20
|
+
"""An event in the A2U stream."""
|
|
21
|
+
event_type: str
|
|
22
|
+
data: Dict[str, Any]
|
|
23
|
+
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
24
|
+
event_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
25
|
+
|
|
26
|
+
def to_sse(self) -> str:
|
|
27
|
+
"""Convert to SSE format."""
|
|
28
|
+
return f"event: {self.event_type}\ndata: {json.dumps(self.data)}\nid: {self.event_id}\n\n"
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
31
|
+
"""Convert to dictionary."""
|
|
32
|
+
return {
|
|
33
|
+
"event_type": self.event_type,
|
|
34
|
+
"data": self.data,
|
|
35
|
+
"timestamp": self.timestamp,
|
|
36
|
+
"event_id": self.event_id,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class A2USubscription:
|
|
42
|
+
"""A subscription to an A2U event stream."""
|
|
43
|
+
subscription_id: str
|
|
44
|
+
stream_name: str
|
|
45
|
+
filters: List[str] = field(default_factory=list)
|
|
46
|
+
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
47
|
+
|
|
48
|
+
def matches_event(self, event: A2UEvent) -> bool:
|
|
49
|
+
"""Check if event matches subscription filters."""
|
|
50
|
+
if not self.filters:
|
|
51
|
+
return True
|
|
52
|
+
return event.event_type in self.filters
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class A2UEventBus:
|
|
56
|
+
"""
|
|
57
|
+
Event bus for A2U event distribution.
|
|
58
|
+
|
|
59
|
+
Manages subscriptions and broadcasts events to subscribers.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
"""Initialize the event bus."""
|
|
64
|
+
self._subscriptions: Dict[str, A2USubscription] = {}
|
|
65
|
+
self._queues: Dict[str, asyncio.Queue] = {}
|
|
66
|
+
self._streams: Dict[str, Set[str]] = {} # stream_name -> subscription_ids
|
|
67
|
+
|
|
68
|
+
def subscribe(
|
|
69
|
+
self,
|
|
70
|
+
stream_name: str = "events",
|
|
71
|
+
filters: Optional[List[str]] = None,
|
|
72
|
+
) -> A2USubscription:
|
|
73
|
+
"""
|
|
74
|
+
Subscribe to an event stream.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
stream_name: Name of the stream to subscribe to
|
|
78
|
+
filters: Optional list of event types to filter
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A2USubscription object
|
|
82
|
+
"""
|
|
83
|
+
subscription_id = f"sub-{uuid.uuid4().hex[:12]}"
|
|
84
|
+
subscription = A2USubscription(
|
|
85
|
+
subscription_id=subscription_id,
|
|
86
|
+
stream_name=stream_name,
|
|
87
|
+
filters=filters or [],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self._subscriptions[subscription_id] = subscription
|
|
91
|
+
self._queues[subscription_id] = asyncio.Queue()
|
|
92
|
+
|
|
93
|
+
if stream_name not in self._streams:
|
|
94
|
+
self._streams[stream_name] = set()
|
|
95
|
+
self._streams[stream_name].add(subscription_id)
|
|
96
|
+
|
|
97
|
+
logger.debug(f"Created subscription {subscription_id} for stream {stream_name}")
|
|
98
|
+
return subscription
|
|
99
|
+
|
|
100
|
+
def unsubscribe(self, subscription_id: str) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Unsubscribe from an event stream.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
subscription_id: ID of the subscription to remove
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if unsubscribed, False if not found
|
|
109
|
+
"""
|
|
110
|
+
if subscription_id not in self._subscriptions:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
subscription = self._subscriptions[subscription_id]
|
|
114
|
+
|
|
115
|
+
# Remove from stream set
|
|
116
|
+
if subscription.stream_name in self._streams:
|
|
117
|
+
self._streams[subscription.stream_name].discard(subscription_id)
|
|
118
|
+
|
|
119
|
+
# Clean up
|
|
120
|
+
del self._subscriptions[subscription_id]
|
|
121
|
+
del self._queues[subscription_id]
|
|
122
|
+
|
|
123
|
+
logger.debug(f"Removed subscription {subscription_id}")
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
async def publish(self, event: A2UEvent, stream_name: str = "events") -> int:
|
|
127
|
+
"""
|
|
128
|
+
Publish an event to a stream.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
event: Event to publish
|
|
132
|
+
stream_name: Name of the stream
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Number of subscribers that received the event
|
|
136
|
+
"""
|
|
137
|
+
if stream_name not in self._streams:
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
count = 0
|
|
141
|
+
for sub_id in self._streams[stream_name]:
|
|
142
|
+
subscription = self._subscriptions.get(sub_id)
|
|
143
|
+
if subscription and subscription.matches_event(event):
|
|
144
|
+
await self._queues[sub_id].put(event)
|
|
145
|
+
count += 1
|
|
146
|
+
|
|
147
|
+
logger.debug(f"Published event {event.event_type} to {count} subscribers")
|
|
148
|
+
return count
|
|
149
|
+
|
|
150
|
+
def publish_sync(self, event: A2UEvent, stream_name: str = "events") -> int:
|
|
151
|
+
"""
|
|
152
|
+
Synchronously publish an event (creates event loop if needed).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
event: Event to publish
|
|
156
|
+
stream_name: Name of the stream
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Number of subscribers that received the event
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
loop = asyncio.get_event_loop()
|
|
163
|
+
if loop.is_running():
|
|
164
|
+
# Schedule in running loop
|
|
165
|
+
asyncio.ensure_future(self.publish(event, stream_name))
|
|
166
|
+
return len(self._streams.get(stream_name, set()))
|
|
167
|
+
else:
|
|
168
|
+
return loop.run_until_complete(self.publish(event, stream_name))
|
|
169
|
+
except RuntimeError:
|
|
170
|
+
# No event loop, create one
|
|
171
|
+
return asyncio.run(self.publish(event, stream_name))
|
|
172
|
+
|
|
173
|
+
async def get_events(
|
|
174
|
+
self,
|
|
175
|
+
subscription_id: str,
|
|
176
|
+
timeout: float = 30.0,
|
|
177
|
+
):
|
|
178
|
+
"""
|
|
179
|
+
Async generator for subscription events.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
subscription_id: ID of the subscription
|
|
183
|
+
timeout: Timeout for waiting for events
|
|
184
|
+
|
|
185
|
+
Yields:
|
|
186
|
+
A2UEvent objects
|
|
187
|
+
"""
|
|
188
|
+
if subscription_id not in self._queues:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
queue = self._queues[subscription_id]
|
|
192
|
+
|
|
193
|
+
while True:
|
|
194
|
+
try:
|
|
195
|
+
event = await asyncio.wait_for(queue.get(), timeout=timeout)
|
|
196
|
+
yield event
|
|
197
|
+
except asyncio.TimeoutError:
|
|
198
|
+
# Send keepalive
|
|
199
|
+
yield A2UEvent(event_type="keepalive", data={})
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Global event bus instance
|
|
203
|
+
_event_bus: Optional[A2UEventBus] = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_event_bus() -> A2UEventBus:
|
|
207
|
+
"""Get or create the global event bus."""
|
|
208
|
+
global _event_bus
|
|
209
|
+
if _event_bus is None:
|
|
210
|
+
_event_bus = A2UEventBus()
|
|
211
|
+
return _event_bus
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def create_a2u_routes(app: Any, event_bus: Optional[A2UEventBus] = None) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Add A2U routes to a FastAPI/Starlette application.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
app: FastAPI or Starlette application
|
|
220
|
+
event_bus: Optional event bus (uses global if not provided)
|
|
221
|
+
"""
|
|
222
|
+
bus = event_bus or get_event_bus()
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
from starlette.responses import JSONResponse, StreamingResponse
|
|
226
|
+
from starlette.routing import Route
|
|
227
|
+
except ImportError:
|
|
228
|
+
try:
|
|
229
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
230
|
+
except ImportError:
|
|
231
|
+
raise ImportError("Starlette or FastAPI required for A2U routes")
|
|
232
|
+
|
|
233
|
+
async def a2u_info(request):
|
|
234
|
+
"""GET /a2u/info - Get A2U server info."""
|
|
235
|
+
return JSONResponse({
|
|
236
|
+
"name": "A2U Event Stream",
|
|
237
|
+
"version": "1.0.0",
|
|
238
|
+
"streams": list(bus._streams.keys()) or ["events"],
|
|
239
|
+
"event_types": [
|
|
240
|
+
"agent.started",
|
|
241
|
+
"agent.thinking",
|
|
242
|
+
"agent.tool_call",
|
|
243
|
+
"agent.response",
|
|
244
|
+
"agent.completed",
|
|
245
|
+
"agent.error",
|
|
246
|
+
],
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
async def a2u_subscribe(request):
|
|
250
|
+
"""POST /a2u/subscribe - Subscribe to an event stream."""
|
|
251
|
+
try:
|
|
252
|
+
body = await request.json()
|
|
253
|
+
except Exception:
|
|
254
|
+
body = {}
|
|
255
|
+
|
|
256
|
+
stream_name = body.get("stream", "events")
|
|
257
|
+
filters = body.get("filters", [])
|
|
258
|
+
|
|
259
|
+
subscription = bus.subscribe(stream_name, filters)
|
|
260
|
+
|
|
261
|
+
base_url = str(request.url).rsplit("/", 1)[0]
|
|
262
|
+
|
|
263
|
+
return JSONResponse({
|
|
264
|
+
"subscription_id": subscription.subscription_id,
|
|
265
|
+
"stream_name": subscription.stream_name,
|
|
266
|
+
"stream_url": f"{base_url}/events/{subscription.subscription_id}",
|
|
267
|
+
"created_at": subscription.created_at,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
async def a2u_unsubscribe(request):
|
|
271
|
+
"""POST /a2u/unsubscribe - Unsubscribe from an event stream."""
|
|
272
|
+
try:
|
|
273
|
+
body = await request.json()
|
|
274
|
+
except Exception:
|
|
275
|
+
return JSONResponse({"error": "Invalid JSON"}, status_code=400)
|
|
276
|
+
|
|
277
|
+
subscription_id = body.get("subscription_id")
|
|
278
|
+
if not subscription_id:
|
|
279
|
+
return JSONResponse({"error": "subscription_id required"}, status_code=400)
|
|
280
|
+
|
|
281
|
+
if bus.unsubscribe(subscription_id):
|
|
282
|
+
return JSONResponse({"status": "unsubscribed"})
|
|
283
|
+
else:
|
|
284
|
+
return JSONResponse({"error": "Subscription not found"}, status_code=404)
|
|
285
|
+
|
|
286
|
+
async def a2u_events_stream(request):
|
|
287
|
+
"""GET /a2u/events/{stream_name} - Stream events via SSE."""
|
|
288
|
+
stream_name = request.path_params.get("stream_name", "events")
|
|
289
|
+
|
|
290
|
+
# Create subscription for this stream
|
|
291
|
+
subscription = bus.subscribe(stream_name)
|
|
292
|
+
|
|
293
|
+
async def event_generator():
|
|
294
|
+
try:
|
|
295
|
+
async for event in bus.get_events(subscription.subscription_id):
|
|
296
|
+
yield event.to_sse()
|
|
297
|
+
finally:
|
|
298
|
+
bus.unsubscribe(subscription.subscription_id)
|
|
299
|
+
|
|
300
|
+
return StreamingResponse(
|
|
301
|
+
event_generator(),
|
|
302
|
+
media_type="text/event-stream",
|
|
303
|
+
headers={
|
|
304
|
+
"Cache-Control": "no-cache",
|
|
305
|
+
"Connection": "keep-alive",
|
|
306
|
+
"X-Accel-Buffering": "no",
|
|
307
|
+
},
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
async def a2u_events_subscription(request):
|
|
311
|
+
"""GET /a2u/events/sub/{subscription_id} - Stream events for subscription."""
|
|
312
|
+
subscription_id = request.path_params.get("subscription_id")
|
|
313
|
+
|
|
314
|
+
if subscription_id not in bus._subscriptions:
|
|
315
|
+
return JSONResponse({"error": "Subscription not found"}, status_code=404)
|
|
316
|
+
|
|
317
|
+
async def event_generator():
|
|
318
|
+
async for event in bus.get_events(subscription_id):
|
|
319
|
+
yield event.to_sse()
|
|
320
|
+
|
|
321
|
+
return StreamingResponse(
|
|
322
|
+
event_generator(),
|
|
323
|
+
media_type="text/event-stream",
|
|
324
|
+
headers={
|
|
325
|
+
"Cache-Control": "no-cache",
|
|
326
|
+
"Connection": "keep-alive",
|
|
327
|
+
"X-Accel-Buffering": "no",
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
async def a2u_health(request):
|
|
332
|
+
"""GET /a2u/health - A2U health check."""
|
|
333
|
+
return JSONResponse({
|
|
334
|
+
"status": "healthy",
|
|
335
|
+
"active_subscriptions": len(bus._subscriptions),
|
|
336
|
+
"active_streams": len(bus._streams),
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
# Add routes based on app type
|
|
340
|
+
if hasattr(app, 'add_api_route'):
|
|
341
|
+
# FastAPI
|
|
342
|
+
app.add_api_route("/a2u/info", a2u_info, methods=["GET"])
|
|
343
|
+
app.add_api_route("/a2u/subscribe", a2u_subscribe, methods=["POST"])
|
|
344
|
+
app.add_api_route("/a2u/unsubscribe", a2u_unsubscribe, methods=["POST"])
|
|
345
|
+
app.add_api_route("/a2u/events/{stream_name}", a2u_events_stream, methods=["GET"])
|
|
346
|
+
app.add_api_route("/a2u/events/sub/{subscription_id}", a2u_events_subscription, methods=["GET"])
|
|
347
|
+
app.add_api_route("/a2u/health", a2u_health, methods=["GET"])
|
|
348
|
+
elif hasattr(app, 'routes'):
|
|
349
|
+
# Starlette
|
|
350
|
+
app.routes.extend([
|
|
351
|
+
Route("/a2u/info", a2u_info, methods=["GET"]),
|
|
352
|
+
Route("/a2u/subscribe", a2u_subscribe, methods=["POST"]),
|
|
353
|
+
Route("/a2u/unsubscribe", a2u_unsubscribe, methods=["POST"]),
|
|
354
|
+
Route("/a2u/events/{stream_name}", a2u_events_stream, methods=["GET"]),
|
|
355
|
+
Route("/a2u/events/sub/{subscription_id}", a2u_events_subscription, methods=["GET"]),
|
|
356
|
+
Route("/a2u/health", a2u_health, methods=["GET"]),
|
|
357
|
+
])
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Helper functions for publishing events
|
|
361
|
+
def emit_agent_event(
|
|
362
|
+
event_type: str,
|
|
363
|
+
data: Dict[str, Any],
|
|
364
|
+
agent_id: Optional[str] = None,
|
|
365
|
+
stream_name: str = "events",
|
|
366
|
+
) -> None:
|
|
367
|
+
"""
|
|
368
|
+
Emit an agent event to the A2U stream.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
event_type: Type of event (e.g., "agent.started", "agent.response")
|
|
372
|
+
data: Event data
|
|
373
|
+
agent_id: Optional agent ID to include
|
|
374
|
+
stream_name: Stream to publish to
|
|
375
|
+
"""
|
|
376
|
+
if agent_id:
|
|
377
|
+
data["agent_id"] = agent_id
|
|
378
|
+
|
|
379
|
+
event = A2UEvent(event_type=event_type, data=data)
|
|
380
|
+
get_event_bus().publish_sync(event, stream_name)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def emit_agent_started(agent_id: str, agent_name: str, **kwargs) -> None:
|
|
384
|
+
"""Emit agent.started event."""
|
|
385
|
+
emit_agent_event("agent.started", {"agent_name": agent_name, **kwargs}, agent_id)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def emit_agent_thinking(agent_id: str, message: str = "", **kwargs) -> None:
|
|
389
|
+
"""Emit agent.thinking event."""
|
|
390
|
+
emit_agent_event("agent.thinking", {"message": message, **kwargs}, agent_id)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def emit_agent_tool_call(agent_id: str, tool_name: str, arguments: Dict = None, **kwargs) -> None:
|
|
394
|
+
"""Emit agent.tool_call event."""
|
|
395
|
+
emit_agent_event("agent.tool_call", {"tool_name": tool_name, "arguments": arguments or {}, **kwargs}, agent_id)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def emit_agent_response(agent_id: str, response: str, **kwargs) -> None:
|
|
399
|
+
"""Emit agent.response event."""
|
|
400
|
+
emit_agent_event("agent.response", {"response": response, **kwargs}, agent_id)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def emit_agent_completed(agent_id: str, result: Any = None, **kwargs) -> None:
|
|
404
|
+
"""Emit agent.completed event."""
|
|
405
|
+
emit_agent_event("agent.completed", {"result": result, **kwargs}, agent_id)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def emit_agent_error(agent_id: str, error: str, **kwargs) -> None:
|
|
409
|
+
"""Emit agent.error event."""
|
|
410
|
+
emit_agent_event("agent.error", {"error": error, **kwargs}, agent_id)
|