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,1019 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Endpoints CLI Feature Handler
|
|
3
|
+
|
|
4
|
+
Provides CLI commands for interacting with PraisonAI endpoints:
|
|
5
|
+
- list: List available endpoints (all provider types)
|
|
6
|
+
- describe: Show endpoint details and schema
|
|
7
|
+
- invoke: Call an endpoint
|
|
8
|
+
- health: Check endpoint server health
|
|
9
|
+
- types: List supported provider types
|
|
10
|
+
|
|
11
|
+
Supported Provider Types:
|
|
12
|
+
- recipe: Recipe runner endpoints
|
|
13
|
+
- agents-api: Single/multi-agent HTTP API
|
|
14
|
+
- mcp: MCP server (stdio, http, sse)
|
|
15
|
+
- tools-mcp: Tools exposed as MCP server
|
|
16
|
+
- a2a: Agent-to-agent protocol
|
|
17
|
+
- a2u: Agent-to-user event stream
|
|
18
|
+
|
|
19
|
+
All commands use the canonical `praisonai endpoints` prefix.
|
|
20
|
+
|
|
21
|
+
Why this feature is valuable:
|
|
22
|
+
- DX: Client invocation from any language/script
|
|
23
|
+
- Ops: Health checks, monitoring, automation
|
|
24
|
+
- Polyglot: Non-Python clients can invoke recipes via HTTP
|
|
25
|
+
- Testing: Easy endpoint verification without code
|
|
26
|
+
- Unified: Single interface for all server types
|
|
27
|
+
|
|
28
|
+
Architecture notes:
|
|
29
|
+
- Optional extras only (no server deps in core)
|
|
30
|
+
- Lazy imports for all HTTP client code
|
|
31
|
+
- No impact on praisonaiagents import time
|
|
32
|
+
- Backward compatible with existing recipe-only usage
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
from typing import Any, Dict, List, Optional
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Supported provider types
|
|
42
|
+
PROVIDER_TYPES = [
|
|
43
|
+
"recipe",
|
|
44
|
+
"agents-api",
|
|
45
|
+
"mcp",
|
|
46
|
+
"tools-mcp",
|
|
47
|
+
"a2a",
|
|
48
|
+
"a2u",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EndpointsHandler:
|
|
53
|
+
"""
|
|
54
|
+
CLI handler for endpoints operations.
|
|
55
|
+
|
|
56
|
+
Commands:
|
|
57
|
+
- list: List available endpoints (all provider types)
|
|
58
|
+
- describe: Show endpoint details
|
|
59
|
+
- invoke: Call an endpoint
|
|
60
|
+
- health: Check server health
|
|
61
|
+
- types: List supported provider types
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# Exit codes
|
|
65
|
+
EXIT_SUCCESS = 0
|
|
66
|
+
EXIT_GENERAL_ERROR = 1
|
|
67
|
+
EXIT_VALIDATION_ERROR = 2
|
|
68
|
+
EXIT_RUNTIME_ERROR = 3
|
|
69
|
+
EXIT_AUTH_ERROR = 4
|
|
70
|
+
EXIT_NOT_FOUND = 7
|
|
71
|
+
EXIT_CONNECTION_ERROR = 8
|
|
72
|
+
|
|
73
|
+
DEFAULT_URL = "http://localhost:8765"
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
"""Initialize the handler."""
|
|
77
|
+
self._base_url = None
|
|
78
|
+
self._api_key = None
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def base_url(self) -> str:
|
|
82
|
+
"""Get base URL from env or default."""
|
|
83
|
+
if self._base_url:
|
|
84
|
+
return self._base_url
|
|
85
|
+
return os.environ.get("PRAISONAI_ENDPOINTS_URL", self.DEFAULT_URL)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def api_key(self) -> Optional[str]:
|
|
89
|
+
"""Get API key from env."""
|
|
90
|
+
if self._api_key:
|
|
91
|
+
return self._api_key
|
|
92
|
+
return os.environ.get("PRAISONAI_ENDPOINTS_API_KEY")
|
|
93
|
+
|
|
94
|
+
def handle(self, args: List[str]) -> int:
|
|
95
|
+
"""
|
|
96
|
+
Handle endpoints subcommand.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
args: Command arguments
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Exit code
|
|
103
|
+
"""
|
|
104
|
+
if not args:
|
|
105
|
+
self._print_help()
|
|
106
|
+
return self.EXIT_SUCCESS
|
|
107
|
+
|
|
108
|
+
command = args[0]
|
|
109
|
+
remaining = args[1:]
|
|
110
|
+
|
|
111
|
+
commands = {
|
|
112
|
+
"list": self.cmd_list,
|
|
113
|
+
"describe": self.cmd_describe,
|
|
114
|
+
"invoke": self.cmd_invoke,
|
|
115
|
+
"health": self.cmd_health,
|
|
116
|
+
"types": self.cmd_types,
|
|
117
|
+
"discovery": self.cmd_discovery,
|
|
118
|
+
"help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
119
|
+
"--help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
120
|
+
"-h": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if command in commands:
|
|
124
|
+
return commands[command](remaining)
|
|
125
|
+
else:
|
|
126
|
+
self._print_error(f"Unknown command: {command}")
|
|
127
|
+
self._print_help()
|
|
128
|
+
return self.EXIT_GENERAL_ERROR
|
|
129
|
+
|
|
130
|
+
def _print_help(self):
|
|
131
|
+
"""Print help message."""
|
|
132
|
+
help_text = """
|
|
133
|
+
[bold cyan]PraisonAI Endpoints[/bold cyan]
|
|
134
|
+
|
|
135
|
+
Unified client CLI for interacting with PraisonAI endpoints.
|
|
136
|
+
|
|
137
|
+
[bold]Usage:[/bold]
|
|
138
|
+
praisonai endpoints <command> [options]
|
|
139
|
+
|
|
140
|
+
[bold]Commands:[/bold]
|
|
141
|
+
list List available endpoints (all types)
|
|
142
|
+
describe <name> Show endpoint details and schema
|
|
143
|
+
invoke <name> Call an endpoint
|
|
144
|
+
health Check server health
|
|
145
|
+
types List supported provider types
|
|
146
|
+
discovery Show raw discovery document
|
|
147
|
+
|
|
148
|
+
[bold]Global Options:[/bold]
|
|
149
|
+
--url <url> Server URL (default: http://localhost:8765)
|
|
150
|
+
--api-key <key> API key (or set PRAISONAI_ENDPOINTS_API_KEY)
|
|
151
|
+
--type <type> Filter by provider type (recipe, agents-api, mcp, etc.)
|
|
152
|
+
|
|
153
|
+
[bold]List Options:[/bold]
|
|
154
|
+
--format json Output as JSON
|
|
155
|
+
--tags <a,b> Filter by tags (comma-separated)
|
|
156
|
+
|
|
157
|
+
[bold]Describe Options:[/bold]
|
|
158
|
+
--schema Show input/output schema only
|
|
159
|
+
|
|
160
|
+
[bold]Invoke Options:[/bold]
|
|
161
|
+
--input <path> Input file path
|
|
162
|
+
--input-json <j> Input as JSON string
|
|
163
|
+
--config k=v Config override (repeatable)
|
|
164
|
+
--json Output as JSON
|
|
165
|
+
--stream Stream output events (SSE)
|
|
166
|
+
--dry-run Validate without executing
|
|
167
|
+
|
|
168
|
+
[bold]Provider Types:[/bold]
|
|
169
|
+
recipe Recipe runner endpoints
|
|
170
|
+
agents-api Single/multi-agent HTTP API
|
|
171
|
+
mcp MCP server (stdio, http, sse)
|
|
172
|
+
tools-mcp Tools exposed as MCP server
|
|
173
|
+
a2a Agent-to-agent protocol
|
|
174
|
+
a2u Agent-to-user event stream
|
|
175
|
+
|
|
176
|
+
[bold]Environment Variables:[/bold]
|
|
177
|
+
PRAISONAI_ENDPOINTS_URL Default server URL
|
|
178
|
+
PRAISONAI_ENDPOINTS_API_KEY API key for authentication
|
|
179
|
+
|
|
180
|
+
[bold]Examples:[/bold]
|
|
181
|
+
praisonai endpoints list
|
|
182
|
+
praisonai endpoints list --type agents-api
|
|
183
|
+
praisonai endpoints list --format json --tags audio,video
|
|
184
|
+
praisonai endpoints describe my-agent
|
|
185
|
+
praisonai endpoints describe my-agent --schema
|
|
186
|
+
praisonai endpoints invoke my-agent --input-json '{"query": "hello"}'
|
|
187
|
+
praisonai endpoints invoke my-recipe --input ./data.json --stream
|
|
188
|
+
praisonai endpoints health
|
|
189
|
+
praisonai endpoints health --url http://localhost:8000
|
|
190
|
+
praisonai endpoints types
|
|
191
|
+
praisonai endpoints discovery
|
|
192
|
+
"""
|
|
193
|
+
self._print_rich(help_text)
|
|
194
|
+
|
|
195
|
+
def _print_rich(self, text: str):
|
|
196
|
+
"""Print with rich formatting if available."""
|
|
197
|
+
try:
|
|
198
|
+
from rich import print as rprint
|
|
199
|
+
rprint(text)
|
|
200
|
+
except ImportError:
|
|
201
|
+
import re
|
|
202
|
+
plain = re.sub(r'\[/?[^\]]+\]', '', text)
|
|
203
|
+
print(plain)
|
|
204
|
+
|
|
205
|
+
def _print_error(self, message: str):
|
|
206
|
+
"""Print error message."""
|
|
207
|
+
try:
|
|
208
|
+
from rich import print as rprint
|
|
209
|
+
rprint(f"[red]Error: {message}[/red]")
|
|
210
|
+
except ImportError:
|
|
211
|
+
print(f"Error: {message}", file=sys.stderr)
|
|
212
|
+
|
|
213
|
+
def _print_success(self, message: str):
|
|
214
|
+
"""Print success message."""
|
|
215
|
+
try:
|
|
216
|
+
from rich import print as rprint
|
|
217
|
+
rprint(f"[green]✓ {message}[/green]")
|
|
218
|
+
except ImportError:
|
|
219
|
+
print(f"✓ {message}")
|
|
220
|
+
|
|
221
|
+
def _print_json(self, data: Any):
|
|
222
|
+
"""Print JSON output."""
|
|
223
|
+
print(json.dumps(data, indent=2, default=str))
|
|
224
|
+
|
|
225
|
+
def _parse_args(self, args: List[str], spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
226
|
+
"""Parse command arguments based on spec."""
|
|
227
|
+
result = {k: v.get("default") for k, v in spec.items()}
|
|
228
|
+
positional_keys = [k for k, v in spec.items() if v.get("positional")]
|
|
229
|
+
positional_idx = 0
|
|
230
|
+
|
|
231
|
+
# Handle repeatable args
|
|
232
|
+
for k, v in spec.items():
|
|
233
|
+
if v.get("repeatable"):
|
|
234
|
+
result[k] = []
|
|
235
|
+
|
|
236
|
+
i = 0
|
|
237
|
+
while i < len(args):
|
|
238
|
+
arg = args[i]
|
|
239
|
+
|
|
240
|
+
if arg.startswith("--"):
|
|
241
|
+
key = arg[2:].replace("-", "_")
|
|
242
|
+
if key in spec:
|
|
243
|
+
if spec[key].get("flag"):
|
|
244
|
+
result[key] = True
|
|
245
|
+
elif spec[key].get("repeatable") and i + 1 < len(args):
|
|
246
|
+
result[key].append(args[i + 1])
|
|
247
|
+
i += 1
|
|
248
|
+
elif i + 1 < len(args):
|
|
249
|
+
result[key] = args[i + 1]
|
|
250
|
+
i += 1
|
|
251
|
+
i += 1
|
|
252
|
+
elif arg.startswith("-") and len(arg) == 2:
|
|
253
|
+
for key, val in spec.items():
|
|
254
|
+
if val.get("short") == arg:
|
|
255
|
+
if val.get("flag"):
|
|
256
|
+
result[key] = True
|
|
257
|
+
elif i + 1 < len(args):
|
|
258
|
+
result[key] = args[i + 1]
|
|
259
|
+
i += 1
|
|
260
|
+
break
|
|
261
|
+
i += 1
|
|
262
|
+
else:
|
|
263
|
+
if positional_idx < len(positional_keys):
|
|
264
|
+
result[positional_keys[positional_idx]] = arg
|
|
265
|
+
positional_idx += 1
|
|
266
|
+
i += 1
|
|
267
|
+
|
|
268
|
+
return result
|
|
269
|
+
|
|
270
|
+
def _make_request(
|
|
271
|
+
self,
|
|
272
|
+
method: str,
|
|
273
|
+
path: str,
|
|
274
|
+
url: Optional[str] = None,
|
|
275
|
+
api_key: Optional[str] = None,
|
|
276
|
+
json_data: Optional[Dict] = None,
|
|
277
|
+
stream: bool = False,
|
|
278
|
+
) -> Dict[str, Any]:
|
|
279
|
+
"""Make HTTP request to endpoint server."""
|
|
280
|
+
try:
|
|
281
|
+
import urllib.request
|
|
282
|
+
import urllib.error
|
|
283
|
+
except ImportError:
|
|
284
|
+
return {"error": "urllib not available"}
|
|
285
|
+
|
|
286
|
+
base = url or self.base_url
|
|
287
|
+
full_url = f"{base.rstrip('/')}{path}"
|
|
288
|
+
|
|
289
|
+
headers = {"Content-Type": "application/json"}
|
|
290
|
+
key = api_key or self.api_key
|
|
291
|
+
if key:
|
|
292
|
+
headers["X-API-Key"] = key
|
|
293
|
+
|
|
294
|
+
data = None
|
|
295
|
+
if json_data:
|
|
296
|
+
data = json.dumps(json_data).encode("utf-8")
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
req = urllib.request.Request(
|
|
300
|
+
full_url,
|
|
301
|
+
data=data,
|
|
302
|
+
headers=headers,
|
|
303
|
+
method=method,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
with urllib.request.urlopen(req, timeout=30) as response:
|
|
307
|
+
body = response.read().decode("utf-8")
|
|
308
|
+
return {"status": response.status, "data": json.loads(body) if body else {}}
|
|
309
|
+
|
|
310
|
+
except urllib.error.HTTPError as e:
|
|
311
|
+
body = e.read().decode("utf-8") if e.fp else ""
|
|
312
|
+
try:
|
|
313
|
+
error_data = json.loads(body) if body else {}
|
|
314
|
+
except json.JSONDecodeError:
|
|
315
|
+
error_data = {"message": body}
|
|
316
|
+
return {"status": e.code, "error": error_data}
|
|
317
|
+
except urllib.error.URLError as e:
|
|
318
|
+
return {"status": 0, "error": {"message": f"Connection error: {e.reason}"}}
|
|
319
|
+
except Exception as e:
|
|
320
|
+
return {"status": 0, "error": {"message": str(e)}}
|
|
321
|
+
|
|
322
|
+
def _stream_request(
|
|
323
|
+
self,
|
|
324
|
+
path: str,
|
|
325
|
+
url: Optional[str] = None,
|
|
326
|
+
api_key: Optional[str] = None,
|
|
327
|
+
json_data: Optional[Dict] = None,
|
|
328
|
+
):
|
|
329
|
+
"""Make streaming HTTP request."""
|
|
330
|
+
try:
|
|
331
|
+
import urllib.request
|
|
332
|
+
except ImportError:
|
|
333
|
+
yield {"error": "urllib not available"}
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
base = url or self.base_url
|
|
337
|
+
full_url = f"{base.rstrip('/')}{path}"
|
|
338
|
+
|
|
339
|
+
headers = {"Content-Type": "application/json", "Accept": "text/event-stream"}
|
|
340
|
+
key = api_key or self.api_key
|
|
341
|
+
if key:
|
|
342
|
+
headers["X-API-Key"] = key
|
|
343
|
+
|
|
344
|
+
data = json.dumps(json_data).encode("utf-8") if json_data else None
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
req = urllib.request.Request(full_url, data=data, headers=headers, method="POST")
|
|
348
|
+
|
|
349
|
+
with urllib.request.urlopen(req, timeout=300) as response:
|
|
350
|
+
buffer = ""
|
|
351
|
+
for line in response:
|
|
352
|
+
line = line.decode("utf-8")
|
|
353
|
+
buffer += line
|
|
354
|
+
|
|
355
|
+
if buffer.endswith("\n\n"):
|
|
356
|
+
# Parse SSE event
|
|
357
|
+
event_type = "message"
|
|
358
|
+
event_data = ""
|
|
359
|
+
|
|
360
|
+
for part in buffer.strip().split("\n"):
|
|
361
|
+
if part.startswith("event:"):
|
|
362
|
+
event_type = part[6:].strip()
|
|
363
|
+
elif part.startswith("data:"):
|
|
364
|
+
event_data = part[5:].strip()
|
|
365
|
+
|
|
366
|
+
if event_data:
|
|
367
|
+
try:
|
|
368
|
+
yield {"event": event_type, "data": json.loads(event_data)}
|
|
369
|
+
except json.JSONDecodeError:
|
|
370
|
+
yield {"event": event_type, "data": event_data}
|
|
371
|
+
|
|
372
|
+
buffer = ""
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
yield {"error": str(e)}
|
|
376
|
+
|
|
377
|
+
def _get_provider(self, provider_type: str, url: Optional[str] = None, api_key: Optional[str] = None):
|
|
378
|
+
"""Get a provider instance."""
|
|
379
|
+
try:
|
|
380
|
+
from praisonai.endpoints import get_provider
|
|
381
|
+
return get_provider(
|
|
382
|
+
provider_type,
|
|
383
|
+
base_url=url or self.base_url,
|
|
384
|
+
api_key=api_key or self.api_key,
|
|
385
|
+
)
|
|
386
|
+
except ImportError:
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
def _try_unified_discovery(self, url: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
|
390
|
+
"""Try to get unified discovery document."""
|
|
391
|
+
result = self._make_request("GET", "/__praisonai__/discovery", url=url)
|
|
392
|
+
if not result.get("error") and result.get("data"):
|
|
393
|
+
return result.get("data")
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
def cmd_list(self, args: List[str]) -> int:
|
|
397
|
+
"""List available endpoints (all provider types)."""
|
|
398
|
+
spec = {
|
|
399
|
+
"format": {"default": "table"},
|
|
400
|
+
"tags": {"default": None},
|
|
401
|
+
"url": {"default": None},
|
|
402
|
+
"type": {"default": None},
|
|
403
|
+
}
|
|
404
|
+
parsed = self._parse_args(args, spec)
|
|
405
|
+
|
|
406
|
+
all_endpoints = []
|
|
407
|
+
|
|
408
|
+
# Try unified discovery first
|
|
409
|
+
discovery = self._try_unified_discovery(parsed["url"])
|
|
410
|
+
if discovery:
|
|
411
|
+
endpoints = discovery.get("endpoints", [])
|
|
412
|
+
for ep in endpoints:
|
|
413
|
+
# Filter by type if specified
|
|
414
|
+
if parsed["type"] and ep.get("provider_type") != parsed["type"]:
|
|
415
|
+
continue
|
|
416
|
+
# Filter by tags if specified
|
|
417
|
+
if parsed["tags"]:
|
|
418
|
+
tag_list = [t.strip() for t in parsed["tags"].split(",")]
|
|
419
|
+
ep_tags = ep.get("tags", [])
|
|
420
|
+
if not any(t in ep_tags for t in tag_list):
|
|
421
|
+
continue
|
|
422
|
+
all_endpoints.append(ep)
|
|
423
|
+
else:
|
|
424
|
+
# Fallback: try recipe endpoint (backward compatibility)
|
|
425
|
+
if not parsed["type"] or parsed["type"] == "recipe":
|
|
426
|
+
path = "/v1/recipes"
|
|
427
|
+
if parsed["tags"]:
|
|
428
|
+
path += f"?tags={parsed['tags']}"
|
|
429
|
+
|
|
430
|
+
result = self._make_request("GET", path, url=parsed["url"])
|
|
431
|
+
|
|
432
|
+
if not result.get("error"):
|
|
433
|
+
recipes = result.get("data", {}).get("recipes", [])
|
|
434
|
+
for r in recipes:
|
|
435
|
+
all_endpoints.append({
|
|
436
|
+
"name": r.get("name", ""),
|
|
437
|
+
"description": r.get("description", ""),
|
|
438
|
+
"provider_type": "recipe",
|
|
439
|
+
"version": r.get("version", "1.0.0"),
|
|
440
|
+
"tags": r.get("tags", []),
|
|
441
|
+
"streaming": ["none", "sse"],
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
# Try agents-api endpoint
|
|
445
|
+
if not parsed["type"] or parsed["type"] == "agents-api":
|
|
446
|
+
result = self._make_request("GET", "/", url=parsed["url"])
|
|
447
|
+
if not result.get("error"):
|
|
448
|
+
data = result.get("data", {})
|
|
449
|
+
for path in data.get("endpoints", []):
|
|
450
|
+
all_endpoints.append({
|
|
451
|
+
"name": path.lstrip("/"),
|
|
452
|
+
"description": f"Agent endpoint at {path}",
|
|
453
|
+
"provider_type": "agents-api",
|
|
454
|
+
"streaming": ["none"],
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
if not all_endpoints:
|
|
458
|
+
result = self._make_request("GET", "/health", url=parsed["url"])
|
|
459
|
+
if result.get("error"):
|
|
460
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
461
|
+
return self.EXIT_CONNECTION_ERROR
|
|
462
|
+
print("No endpoints available.")
|
|
463
|
+
return self.EXIT_SUCCESS
|
|
464
|
+
|
|
465
|
+
if parsed["format"] == "json":
|
|
466
|
+
self._print_json(all_endpoints)
|
|
467
|
+
return self.EXIT_SUCCESS
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
from rich.console import Console
|
|
471
|
+
from rich.table import Table
|
|
472
|
+
|
|
473
|
+
console = Console()
|
|
474
|
+
table = Table(title="Available Endpoints")
|
|
475
|
+
table.add_column("Name", style="cyan")
|
|
476
|
+
table.add_column("Type", style="magenta")
|
|
477
|
+
table.add_column("Description")
|
|
478
|
+
table.add_column("Streaming", style="green")
|
|
479
|
+
table.add_column("Tags", style="yellow")
|
|
480
|
+
|
|
481
|
+
for ep in all_endpoints:
|
|
482
|
+
tags = ep.get("tags", [])
|
|
483
|
+
streaming = ep.get("streaming", ["none"])
|
|
484
|
+
desc = ep.get("description", "")
|
|
485
|
+
table.add_row(
|
|
486
|
+
ep.get("name", ""),
|
|
487
|
+
ep.get("provider_type", "unknown"),
|
|
488
|
+
(desc[:40] + "...") if len(desc) > 40 else desc,
|
|
489
|
+
", ".join(streaming[:2]),
|
|
490
|
+
", ".join(tags[:3]) if tags else "",
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
console.print(table)
|
|
494
|
+
except ImportError:
|
|
495
|
+
for ep in all_endpoints:
|
|
496
|
+
print(f"[{ep.get('provider_type', 'unknown')}] {ep.get('name')}: {ep.get('description', '')}")
|
|
497
|
+
|
|
498
|
+
return self.EXIT_SUCCESS
|
|
499
|
+
|
|
500
|
+
def cmd_describe(self, args: List[str]) -> int:
|
|
501
|
+
"""Describe an endpoint."""
|
|
502
|
+
spec = {
|
|
503
|
+
"name": {"positional": True, "default": ""},
|
|
504
|
+
"schema": {"flag": True, "default": False},
|
|
505
|
+
"url": {"default": None},
|
|
506
|
+
"type": {"default": None},
|
|
507
|
+
}
|
|
508
|
+
parsed = self._parse_args(args, spec)
|
|
509
|
+
|
|
510
|
+
if not parsed["name"]:
|
|
511
|
+
self._print_error("Endpoint name required")
|
|
512
|
+
return self.EXIT_VALIDATION_ERROR
|
|
513
|
+
|
|
514
|
+
endpoint_name = parsed["name"]
|
|
515
|
+
endpoint_info = None
|
|
516
|
+
|
|
517
|
+
# Try unified discovery first
|
|
518
|
+
discovery = self._try_unified_discovery(parsed["url"])
|
|
519
|
+
if discovery:
|
|
520
|
+
for ep in discovery.get("endpoints", []):
|
|
521
|
+
if ep.get("name") == endpoint_name:
|
|
522
|
+
if parsed["type"] and ep.get("provider_type") != parsed["type"]:
|
|
523
|
+
continue
|
|
524
|
+
endpoint_info = ep
|
|
525
|
+
break
|
|
526
|
+
|
|
527
|
+
if endpoint_info:
|
|
528
|
+
if parsed["schema"]:
|
|
529
|
+
schema_info = {
|
|
530
|
+
"name": endpoint_info.get("name"),
|
|
531
|
+
"input_schema": endpoint_info.get("input_schema"),
|
|
532
|
+
"output_schema": endpoint_info.get("output_schema"),
|
|
533
|
+
}
|
|
534
|
+
self._print_json(schema_info)
|
|
535
|
+
else:
|
|
536
|
+
self._print_json(endpoint_info)
|
|
537
|
+
return self.EXIT_SUCCESS
|
|
538
|
+
|
|
539
|
+
# Fallback: try recipe endpoint (backward compatibility)
|
|
540
|
+
if not parsed["type"] or parsed["type"] == "recipe":
|
|
541
|
+
if parsed["schema"]:
|
|
542
|
+
path = f"/v1/recipes/{endpoint_name}/schema"
|
|
543
|
+
else:
|
|
544
|
+
path = f"/v1/recipes/{endpoint_name}"
|
|
545
|
+
|
|
546
|
+
result = self._make_request("GET", path, url=parsed["url"])
|
|
547
|
+
|
|
548
|
+
if not result.get("error") and result.get("status") != 404:
|
|
549
|
+
data = result.get("data", {})
|
|
550
|
+
# Add provider_type for consistency
|
|
551
|
+
data["provider_type"] = "recipe"
|
|
552
|
+
self._print_json(data)
|
|
553
|
+
return self.EXIT_SUCCESS
|
|
554
|
+
|
|
555
|
+
# Try agents-api
|
|
556
|
+
if not parsed["type"] or parsed["type"] == "agents-api":
|
|
557
|
+
# Return basic info for agent endpoints
|
|
558
|
+
endpoint_info = {
|
|
559
|
+
"name": endpoint_name,
|
|
560
|
+
"provider_type": "agents-api",
|
|
561
|
+
"description": f"Agent endpoint: {endpoint_name}",
|
|
562
|
+
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
|
|
563
|
+
"streaming": ["none"],
|
|
564
|
+
"auth_modes": ["none"],
|
|
565
|
+
}
|
|
566
|
+
self._print_json(endpoint_info)
|
|
567
|
+
return self.EXIT_SUCCESS
|
|
568
|
+
|
|
569
|
+
self._print_error(f"Endpoint not found: {endpoint_name}")
|
|
570
|
+
return self.EXIT_NOT_FOUND
|
|
571
|
+
|
|
572
|
+
def cmd_invoke(self, args: List[str]) -> int:
|
|
573
|
+
"""Invoke an endpoint."""
|
|
574
|
+
spec = {
|
|
575
|
+
"name": {"positional": True, "default": ""},
|
|
576
|
+
"input": {"short": "-i", "default": None},
|
|
577
|
+
"input_json": {"default": None},
|
|
578
|
+
"config": {"repeatable": True, "default": []},
|
|
579
|
+
"json": {"flag": True, "default": False},
|
|
580
|
+
"stream": {"flag": True, "default": False},
|
|
581
|
+
"url": {"default": None},
|
|
582
|
+
"api_key": {"default": None},
|
|
583
|
+
"dry_run": {"flag": True, "default": False},
|
|
584
|
+
"type": {"default": None},
|
|
585
|
+
}
|
|
586
|
+
parsed = self._parse_args(args, spec)
|
|
587
|
+
|
|
588
|
+
if not parsed["name"]:
|
|
589
|
+
self._print_error("Endpoint name required")
|
|
590
|
+
return self.EXIT_VALIDATION_ERROR
|
|
591
|
+
|
|
592
|
+
endpoint_name = parsed["name"]
|
|
593
|
+
|
|
594
|
+
# Build input data
|
|
595
|
+
input_data = {}
|
|
596
|
+
if parsed["input_json"]:
|
|
597
|
+
try:
|
|
598
|
+
input_data = json.loads(parsed["input_json"])
|
|
599
|
+
except json.JSONDecodeError:
|
|
600
|
+
self._print_error("Invalid JSON in --input-json")
|
|
601
|
+
return self.EXIT_VALIDATION_ERROR
|
|
602
|
+
elif parsed["input"]:
|
|
603
|
+
# Check if input is a file path
|
|
604
|
+
if os.path.isfile(parsed["input"]):
|
|
605
|
+
try:
|
|
606
|
+
with open(parsed["input"]) as f:
|
|
607
|
+
input_data = json.load(f)
|
|
608
|
+
except (json.JSONDecodeError, IOError):
|
|
609
|
+
input_data = {"input": parsed["input"]}
|
|
610
|
+
else:
|
|
611
|
+
input_data = {"input": parsed["input"]}
|
|
612
|
+
|
|
613
|
+
# Build config from --config key=value pairs
|
|
614
|
+
config = {}
|
|
615
|
+
for cfg in parsed["config"]:
|
|
616
|
+
if "=" in cfg:
|
|
617
|
+
k, v = cfg.split("=", 1)
|
|
618
|
+
config[k] = v
|
|
619
|
+
|
|
620
|
+
# Detect provider type from discovery or explicit --type
|
|
621
|
+
provider_type = parsed["type"]
|
|
622
|
+
if not provider_type:
|
|
623
|
+
discovery = self._try_unified_discovery(parsed["url"])
|
|
624
|
+
if discovery:
|
|
625
|
+
for ep in discovery.get("endpoints", []):
|
|
626
|
+
if ep.get("name") == endpoint_name:
|
|
627
|
+
provider_type = ep.get("provider_type")
|
|
628
|
+
break
|
|
629
|
+
|
|
630
|
+
# Default to recipe for backward compatibility
|
|
631
|
+
if not provider_type:
|
|
632
|
+
provider_type = "recipe"
|
|
633
|
+
|
|
634
|
+
# Route to appropriate invocation method
|
|
635
|
+
if provider_type == "recipe":
|
|
636
|
+
return self._invoke_recipe(endpoint_name, input_data, config, parsed)
|
|
637
|
+
elif provider_type == "agents-api":
|
|
638
|
+
return self._invoke_agents_api(endpoint_name, input_data, config, parsed)
|
|
639
|
+
elif provider_type in ("mcp", "tools-mcp"):
|
|
640
|
+
return self._invoke_mcp(endpoint_name, input_data, config, parsed)
|
|
641
|
+
elif provider_type == "a2a":
|
|
642
|
+
return self._invoke_a2a(endpoint_name, input_data, config, parsed)
|
|
643
|
+
elif provider_type == "a2u":
|
|
644
|
+
return self._invoke_a2u(endpoint_name, input_data, config, parsed)
|
|
645
|
+
else:
|
|
646
|
+
# Fallback to recipe
|
|
647
|
+
return self._invoke_recipe(endpoint_name, input_data, config, parsed)
|
|
648
|
+
|
|
649
|
+
def _invoke_recipe(self, name: str, input_data: Dict, config: Dict, parsed: Dict) -> int:
|
|
650
|
+
"""Invoke a recipe endpoint."""
|
|
651
|
+
body = {
|
|
652
|
+
"recipe": name,
|
|
653
|
+
"input": input_data,
|
|
654
|
+
"config": config,
|
|
655
|
+
"options": {"dry_run": parsed["dry_run"]},
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if parsed["stream"]:
|
|
659
|
+
return self._invoke_stream(body, parsed)
|
|
660
|
+
|
|
661
|
+
result = self._make_request(
|
|
662
|
+
"POST",
|
|
663
|
+
"/v1/recipes/run",
|
|
664
|
+
url=parsed["url"],
|
|
665
|
+
api_key=parsed["api_key"],
|
|
666
|
+
json_data=body,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
return self._handle_invoke_result(result, name, parsed)
|
|
670
|
+
|
|
671
|
+
def _invoke_agents_api(self, name: str, input_data: Dict, config: Dict, parsed: Dict) -> int:
|
|
672
|
+
"""Invoke an agents-api endpoint."""
|
|
673
|
+
path = name if name.startswith("/") else f"/{name}"
|
|
674
|
+
|
|
675
|
+
# Build body - agents-api expects query field
|
|
676
|
+
body = input_data
|
|
677
|
+
if "query" not in body and config.get("query"):
|
|
678
|
+
body["query"] = config["query"]
|
|
679
|
+
|
|
680
|
+
result = self._make_request(
|
|
681
|
+
"POST",
|
|
682
|
+
path,
|
|
683
|
+
url=parsed["url"],
|
|
684
|
+
api_key=parsed["api_key"],
|
|
685
|
+
json_data=body,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
if result.get("status") == 401:
|
|
689
|
+
self._print_error("Authentication required")
|
|
690
|
+
return self.EXIT_AUTH_ERROR
|
|
691
|
+
|
|
692
|
+
if result.get("status") == 404:
|
|
693
|
+
self._print_error(f"Endpoint not found: {name}")
|
|
694
|
+
return self.EXIT_NOT_FOUND
|
|
695
|
+
|
|
696
|
+
if result.get("error"):
|
|
697
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
698
|
+
return self.EXIT_CONNECTION_ERROR
|
|
699
|
+
|
|
700
|
+
data = result.get("data", {})
|
|
701
|
+
|
|
702
|
+
if parsed["json"]:
|
|
703
|
+
self._print_json(data)
|
|
704
|
+
else:
|
|
705
|
+
response = data.get("response", data)
|
|
706
|
+
self._print_success(f"Endpoint '{name}' invoked successfully")
|
|
707
|
+
print(f" Response: {response}")
|
|
708
|
+
|
|
709
|
+
return self.EXIT_SUCCESS
|
|
710
|
+
|
|
711
|
+
def _invoke_mcp(self, name: str, input_data: Dict, config: Dict, parsed: Dict) -> int:
|
|
712
|
+
"""Invoke an MCP tool."""
|
|
713
|
+
body = {
|
|
714
|
+
"tool": name,
|
|
715
|
+
"arguments": input_data,
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
result = self._make_request(
|
|
719
|
+
"POST",
|
|
720
|
+
"/mcp/tools/call",
|
|
721
|
+
url=parsed["url"],
|
|
722
|
+
api_key=parsed["api_key"],
|
|
723
|
+
json_data=body,
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
if result.get("error"):
|
|
727
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
728
|
+
return self.EXIT_CONNECTION_ERROR
|
|
729
|
+
|
|
730
|
+
data = result.get("data", {})
|
|
731
|
+
|
|
732
|
+
if parsed["json"]:
|
|
733
|
+
self._print_json(data)
|
|
734
|
+
else:
|
|
735
|
+
self._print_success(f"Tool '{name}' invoked successfully")
|
|
736
|
+
print(f" Result: {data.get('result', data)}")
|
|
737
|
+
|
|
738
|
+
return self.EXIT_SUCCESS
|
|
739
|
+
|
|
740
|
+
def _invoke_a2a(self, name: str, input_data: Dict, config: Dict, parsed: Dict) -> int:
|
|
741
|
+
"""Invoke an A2A agent."""
|
|
742
|
+
import uuid
|
|
743
|
+
|
|
744
|
+
message = input_data.get("message", input_data.get("query", ""))
|
|
745
|
+
if not message and config.get("message"):
|
|
746
|
+
message = config["message"]
|
|
747
|
+
|
|
748
|
+
body = {
|
|
749
|
+
"jsonrpc": "2.0",
|
|
750
|
+
"method": "message/send",
|
|
751
|
+
"id": str(uuid.uuid4()),
|
|
752
|
+
"params": {
|
|
753
|
+
"message": {
|
|
754
|
+
"role": "user",
|
|
755
|
+
"parts": [{"type": "text", "text": message}],
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
result = self._make_request(
|
|
761
|
+
"POST",
|
|
762
|
+
"/a2a",
|
|
763
|
+
url=parsed["url"],
|
|
764
|
+
api_key=parsed["api_key"],
|
|
765
|
+
json_data=body,
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
if result.get("error"):
|
|
769
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
770
|
+
return self.EXIT_CONNECTION_ERROR
|
|
771
|
+
|
|
772
|
+
data = result.get("data", {})
|
|
773
|
+
|
|
774
|
+
if parsed["json"]:
|
|
775
|
+
self._print_json(data)
|
|
776
|
+
else:
|
|
777
|
+
if "result" in data:
|
|
778
|
+
self._print_success(f"A2A message sent to '{name}'")
|
|
779
|
+
print(f" Result: {data['result']}")
|
|
780
|
+
elif "error" in data:
|
|
781
|
+
self._print_error(data["error"].get("message", str(data["error"])))
|
|
782
|
+
return self.EXIT_RUNTIME_ERROR
|
|
783
|
+
|
|
784
|
+
return self.EXIT_SUCCESS
|
|
785
|
+
|
|
786
|
+
def _invoke_a2u(self, name: str, input_data: Dict, config: Dict, parsed: Dict) -> int:
|
|
787
|
+
"""Subscribe to an A2U event stream."""
|
|
788
|
+
if parsed["stream"]:
|
|
789
|
+
return self._stream_a2u(name, parsed)
|
|
790
|
+
|
|
791
|
+
body = {
|
|
792
|
+
"stream": name,
|
|
793
|
+
"filters": input_data.get("filters", []),
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
result = self._make_request(
|
|
797
|
+
"POST",
|
|
798
|
+
"/a2u/subscribe",
|
|
799
|
+
url=parsed["url"],
|
|
800
|
+
api_key=parsed["api_key"],
|
|
801
|
+
json_data=body,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
if result.get("error"):
|
|
805
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
806
|
+
return self.EXIT_CONNECTION_ERROR
|
|
807
|
+
|
|
808
|
+
data = result.get("data", {})
|
|
809
|
+
|
|
810
|
+
if parsed["json"]:
|
|
811
|
+
self._print_json(data)
|
|
812
|
+
else:
|
|
813
|
+
self._print_success(f"Subscribed to A2U stream '{name}'")
|
|
814
|
+
print(f" Stream URL: {data.get('stream_url')}")
|
|
815
|
+
|
|
816
|
+
return self.EXIT_SUCCESS
|
|
817
|
+
|
|
818
|
+
def _stream_a2u(self, name: str, parsed: Dict) -> int:
|
|
819
|
+
"""Stream A2U events."""
|
|
820
|
+
print(f"Streaming events from '{name}'...")
|
|
821
|
+
|
|
822
|
+
for event in self._stream_request(
|
|
823
|
+
f"/a2u/events/{name}",
|
|
824
|
+
url=parsed["url"],
|
|
825
|
+
api_key=parsed["api_key"],
|
|
826
|
+
):
|
|
827
|
+
if event.get("error"):
|
|
828
|
+
self._print_error(event["error"])
|
|
829
|
+
return self.EXIT_CONNECTION_ERROR
|
|
830
|
+
|
|
831
|
+
if parsed["json"]:
|
|
832
|
+
print(json.dumps(event))
|
|
833
|
+
else:
|
|
834
|
+
print(f" [{event.get('event', 'event')}] {event.get('data', '')}")
|
|
835
|
+
|
|
836
|
+
return self.EXIT_SUCCESS
|
|
837
|
+
|
|
838
|
+
def _handle_invoke_result(self, result: Dict, name: str, parsed: Dict) -> int:
|
|
839
|
+
"""Handle common invoke result processing."""
|
|
840
|
+
if result.get("status") == 401:
|
|
841
|
+
self._print_error("Authentication required. Use --api-key or set PRAISONAI_ENDPOINTS_API_KEY")
|
|
842
|
+
return self.EXIT_AUTH_ERROR
|
|
843
|
+
|
|
844
|
+
if result.get("status") == 404:
|
|
845
|
+
self._print_error(f"Endpoint not found: {name}")
|
|
846
|
+
return self.EXIT_NOT_FOUND
|
|
847
|
+
|
|
848
|
+
if result.get("error"):
|
|
849
|
+
self._print_error(result["error"].get("message", str(result["error"])))
|
|
850
|
+
return self.EXIT_CONNECTION_ERROR
|
|
851
|
+
|
|
852
|
+
data = result.get("data", {})
|
|
853
|
+
|
|
854
|
+
if parsed["json"]:
|
|
855
|
+
self._print_json(data)
|
|
856
|
+
else:
|
|
857
|
+
if data.get("ok", True):
|
|
858
|
+
self._print_success(f"Endpoint '{name}' invoked successfully")
|
|
859
|
+
if data.get("run_id"):
|
|
860
|
+
print(f" Run ID: {data.get('run_id')}")
|
|
861
|
+
if data.get("status"):
|
|
862
|
+
print(f" Status: {data.get('status')}")
|
|
863
|
+
if data.get("output"):
|
|
864
|
+
print(f" Output: {data.get('output')}")
|
|
865
|
+
else:
|
|
866
|
+
self._print_error(f"Invocation failed: {data.get('error')}")
|
|
867
|
+
return self.EXIT_RUNTIME_ERROR
|
|
868
|
+
|
|
869
|
+
return self.EXIT_SUCCESS if data.get("ok", True) else self.EXIT_RUNTIME_ERROR
|
|
870
|
+
|
|
871
|
+
def _invoke_stream(self, body: Dict, parsed: Dict) -> int:
|
|
872
|
+
"""Handle streaming invocation."""
|
|
873
|
+
print("Streaming output...")
|
|
874
|
+
|
|
875
|
+
for event in self._stream_request(
|
|
876
|
+
"/v1/recipes/stream",
|
|
877
|
+
url=parsed["url"],
|
|
878
|
+
api_key=parsed["api_key"],
|
|
879
|
+
json_data=body,
|
|
880
|
+
):
|
|
881
|
+
if event.get("error"):
|
|
882
|
+
self._print_error(event["error"])
|
|
883
|
+
return self.EXIT_CONNECTION_ERROR
|
|
884
|
+
|
|
885
|
+
event_type = event.get("event", "message")
|
|
886
|
+
data = event.get("data", {})
|
|
887
|
+
|
|
888
|
+
if parsed["json"]:
|
|
889
|
+
print(json.dumps({"event": event_type, "data": data}))
|
|
890
|
+
else:
|
|
891
|
+
if event_type == "started":
|
|
892
|
+
print(f" Started: {data.get('run_id')}")
|
|
893
|
+
elif event_type == "progress":
|
|
894
|
+
print(f" [{data.get('step')}] {data.get('message', '')}")
|
|
895
|
+
elif event_type == "completed":
|
|
896
|
+
self._print_success(f"Completed: {data.get('status')}")
|
|
897
|
+
elif event_type == "error":
|
|
898
|
+
self._print_error(data.get("message", "Unknown error"))
|
|
899
|
+
return self.EXIT_RUNTIME_ERROR
|
|
900
|
+
|
|
901
|
+
return self.EXIT_SUCCESS
|
|
902
|
+
|
|
903
|
+
def cmd_health(self, args: List[str]) -> int:
|
|
904
|
+
"""Check endpoint server health."""
|
|
905
|
+
spec = {
|
|
906
|
+
"url": {"default": None},
|
|
907
|
+
"type": {"default": None},
|
|
908
|
+
"format": {"default": "table"},
|
|
909
|
+
}
|
|
910
|
+
parsed = self._parse_args(args, spec)
|
|
911
|
+
|
|
912
|
+
# Try unified discovery first for richer info
|
|
913
|
+
discovery = self._try_unified_discovery(parsed["url"])
|
|
914
|
+
|
|
915
|
+
result = self._make_request("GET", "/health", url=parsed["url"])
|
|
916
|
+
|
|
917
|
+
if result.get("error"):
|
|
918
|
+
self._print_error(f"Server unhealthy: {result['error'].get('message', str(result['error']))}")
|
|
919
|
+
return self.EXIT_CONNECTION_ERROR
|
|
920
|
+
|
|
921
|
+
data = result.get("data", {})
|
|
922
|
+
|
|
923
|
+
# Merge discovery info
|
|
924
|
+
if discovery:
|
|
925
|
+
data["schema_version"] = discovery.get("schema_version")
|
|
926
|
+
data["providers"] = [p.get("type") for p in discovery.get("providers", [])]
|
|
927
|
+
data["endpoint_count"] = len(discovery.get("endpoints", []))
|
|
928
|
+
|
|
929
|
+
if parsed["format"] == "json":
|
|
930
|
+
self._print_json(data)
|
|
931
|
+
return self.EXIT_SUCCESS
|
|
932
|
+
|
|
933
|
+
status = data.get("status", "unknown")
|
|
934
|
+
if status in ("healthy", "ok"):
|
|
935
|
+
self._print_success("Server healthy")
|
|
936
|
+
if data.get("service"):
|
|
937
|
+
print(f" Service: {data.get('service')}")
|
|
938
|
+
if data.get("server_name"):
|
|
939
|
+
print(f" Server: {data.get('server_name')}")
|
|
940
|
+
if data.get("version"):
|
|
941
|
+
print(f" Version: {data.get('version')}")
|
|
942
|
+
if data.get("schema_version"):
|
|
943
|
+
print(f" Schema Version: {data.get('schema_version')}")
|
|
944
|
+
if data.get("providers"):
|
|
945
|
+
print(f" Providers: {', '.join(data.get('providers'))}")
|
|
946
|
+
if data.get("endpoint_count"):
|
|
947
|
+
print(f" Endpoints: {data.get('endpoint_count')}")
|
|
948
|
+
if data.get("endpoints"):
|
|
949
|
+
print(f" Endpoints: {', '.join(data.get('endpoints')[:5])}")
|
|
950
|
+
return self.EXIT_SUCCESS
|
|
951
|
+
else:
|
|
952
|
+
self._print_error(f"Server unhealthy: {data}")
|
|
953
|
+
return self.EXIT_RUNTIME_ERROR
|
|
954
|
+
|
|
955
|
+
def cmd_types(self, args: List[str]) -> int:
|
|
956
|
+
"""List supported provider types."""
|
|
957
|
+
spec = {
|
|
958
|
+
"format": {"default": "table"},
|
|
959
|
+
}
|
|
960
|
+
parsed = self._parse_args(args, spec)
|
|
961
|
+
|
|
962
|
+
types_info = [
|
|
963
|
+
{"type": "recipe", "description": "Recipe runner endpoints", "capabilities": ["list", "describe", "invoke", "stream"]},
|
|
964
|
+
{"type": "agents-api", "description": "Single/multi-agent HTTP API", "capabilities": ["list", "describe", "invoke"]},
|
|
965
|
+
{"type": "mcp", "description": "MCP server (stdio, http, sse)", "capabilities": ["list-tools", "call-tool"]},
|
|
966
|
+
{"type": "tools-mcp", "description": "Tools exposed as MCP server", "capabilities": ["list-tools", "call-tool"]},
|
|
967
|
+
{"type": "a2a", "description": "Agent-to-agent protocol", "capabilities": ["agent-card", "message-send", "stream"]},
|
|
968
|
+
{"type": "a2u", "description": "Agent-to-user event stream", "capabilities": ["subscribe", "stream"]},
|
|
969
|
+
]
|
|
970
|
+
|
|
971
|
+
if parsed["format"] == "json":
|
|
972
|
+
self._print_json(types_info)
|
|
973
|
+
return self.EXIT_SUCCESS
|
|
974
|
+
|
|
975
|
+
try:
|
|
976
|
+
from rich.console import Console
|
|
977
|
+
from rich.table import Table
|
|
978
|
+
|
|
979
|
+
console = Console()
|
|
980
|
+
table = Table(title="Supported Provider Types")
|
|
981
|
+
table.add_column("Type", style="cyan")
|
|
982
|
+
table.add_column("Description")
|
|
983
|
+
table.add_column("Capabilities", style="green")
|
|
984
|
+
|
|
985
|
+
for t in types_info:
|
|
986
|
+
table.add_row(
|
|
987
|
+
t["type"],
|
|
988
|
+
t["description"],
|
|
989
|
+
", ".join(t["capabilities"]),
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
console.print(table)
|
|
993
|
+
except ImportError:
|
|
994
|
+
for t in types_info:
|
|
995
|
+
print(f"{t['type']}: {t['description']}")
|
|
996
|
+
|
|
997
|
+
return self.EXIT_SUCCESS
|
|
998
|
+
|
|
999
|
+
def cmd_discovery(self, args: List[str]) -> int:
|
|
1000
|
+
"""Show raw discovery document."""
|
|
1001
|
+
spec = {
|
|
1002
|
+
"url": {"default": None},
|
|
1003
|
+
}
|
|
1004
|
+
parsed = self._parse_args(args, spec)
|
|
1005
|
+
|
|
1006
|
+
discovery = self._try_unified_discovery(parsed["url"])
|
|
1007
|
+
|
|
1008
|
+
if discovery:
|
|
1009
|
+
self._print_json(discovery)
|
|
1010
|
+
return self.EXIT_SUCCESS
|
|
1011
|
+
|
|
1012
|
+
self._print_error("Discovery endpoint not available at this server")
|
|
1013
|
+
return self.EXIT_NOT_FOUND
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def handle_endpoints_command(args: List[str]) -> int:
|
|
1017
|
+
"""Entry point for endpoints command."""
|
|
1018
|
+
handler = EndpointsHandler()
|
|
1019
|
+
return handler.handle(args)
|