PraisonAI 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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)