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,230 @@
1
+ """
2
+ Job Models for PraisonAI Async Jobs API.
3
+
4
+ Defines the data structures for job submission, status, and results.
5
+ """
6
+
7
+ import uuid
8
+ from enum import Enum
9
+ from typing import Optional, Dict, Any, List
10
+ from datetime import datetime
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class JobStatus(str, Enum):
15
+ """Status of a job."""
16
+ QUEUED = "queued"
17
+ RUNNING = "running"
18
+ SUCCEEDED = "succeeded"
19
+ FAILED = "failed"
20
+ CANCELLED = "cancelled"
21
+
22
+
23
+ class JobSubmitRequest(BaseModel):
24
+ """Request body for submitting a new job."""
25
+ prompt: str = Field(..., description="The prompt or task for the agent")
26
+ agent_file: Optional[str] = Field(None, description="Path to agents.yaml file")
27
+ agent_yaml: Optional[str] = Field(None, description="Inline agent YAML configuration")
28
+ recipe_name: Optional[str] = Field(None, description="Recipe name to execute (mutually exclusive with agent_file)")
29
+ recipe_config: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Recipe configuration overrides")
30
+ framework: Optional[str] = Field("praisonai", description="Framework to use (praisonai, crewai, autogen)")
31
+ config: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional configuration")
32
+ webhook_url: Optional[str] = Field(None, description="URL to POST results when complete")
33
+ timeout: Optional[int] = Field(3600, description="Timeout in seconds (default: 1 hour)")
34
+ session_id: Optional[str] = Field(None, description="Session ID for conversation continuity")
35
+ idempotency_key: Optional[str] = Field(None, description="Idempotency key to prevent duplicate submissions")
36
+ idempotency_scope: Optional[str] = Field("none", description="Idempotency scope: none, session, global")
37
+
38
+
39
+ class JobSubmitResponse(BaseModel):
40
+ """Response after submitting a job."""
41
+ job_id: str = Field(..., description="Unique job identifier")
42
+ status: JobStatus = Field(..., description="Current job status")
43
+ created_at: datetime = Field(..., description="Job creation timestamp")
44
+ poll_url: str = Field(..., description="URL to poll for status")
45
+ stream_url: str = Field(..., description="URL for SSE streaming")
46
+
47
+
48
+ class JobProgress(BaseModel):
49
+ """Progress information for a running job."""
50
+ percentage: float = Field(0.0, ge=0.0, le=100.0, description="Completion percentage")
51
+ current_step: Optional[str] = Field(None, description="Current step description")
52
+ steps_completed: int = Field(0, description="Number of steps completed")
53
+ steps_total: Optional[int] = Field(None, description="Total number of steps")
54
+
55
+
56
+ class JobStatusResponse(BaseModel):
57
+ """Response for job status query."""
58
+ job_id: str = Field(..., description="Unique job identifier")
59
+ status: JobStatus = Field(..., description="Current job status")
60
+ progress: JobProgress = Field(default_factory=JobProgress, description="Progress information")
61
+ created_at: datetime = Field(..., description="Job creation timestamp")
62
+ started_at: Optional[datetime] = Field(None, description="When job started running")
63
+ completed_at: Optional[datetime] = Field(None, description="When job completed")
64
+ agent_id: Optional[str] = Field(None, description="Current agent ID")
65
+ run_id: Optional[str] = Field(None, description="Run ID for tracing")
66
+ session_id: Optional[str] = Field(None, description="Session ID")
67
+ error: Optional[str] = Field(None, description="Error message if failed")
68
+ retry_after: Optional[int] = Field(None, description="Suggested seconds before next poll")
69
+
70
+
71
+ class JobResultResponse(BaseModel):
72
+ """Response for job result query."""
73
+ job_id: str = Field(..., description="Unique job identifier")
74
+ status: JobStatus = Field(..., description="Final job status")
75
+ result: Optional[Any] = Field(None, description="Job result/output")
76
+ result_url: Optional[str] = Field(None, description="URL to fetch large results")
77
+ metrics: Optional[Dict[str, Any]] = Field(None, description="Execution metrics")
78
+ created_at: datetime = Field(..., description="Job creation timestamp")
79
+ started_at: Optional[datetime] = Field(None, description="When job started running")
80
+ completed_at: Optional[datetime] = Field(None, description="When job completed")
81
+ duration_seconds: Optional[float] = Field(None, description="Total execution time")
82
+ error: Optional[str] = Field(None, description="Error message if failed")
83
+
84
+
85
+ class JobListResponse(BaseModel):
86
+ """Response for listing jobs."""
87
+ jobs: List[JobStatusResponse] = Field(..., description="List of jobs")
88
+ total: int = Field(..., description="Total number of jobs matching filter")
89
+ page: int = Field(1, description="Current page number")
90
+ page_size: int = Field(20, description="Number of jobs per page")
91
+
92
+
93
+ class Job(BaseModel):
94
+ """Internal job representation with full state."""
95
+ id: str = Field(default_factory=lambda: f"run_{uuid.uuid4().hex[:12]}")
96
+ status: JobStatus = Field(default=JobStatus.QUEUED)
97
+
98
+ # Request data
99
+ prompt: str = Field(...)
100
+ agent_file: Optional[str] = Field(None)
101
+ agent_yaml: Optional[str] = Field(None)
102
+ recipe_name: Optional[str] = Field(None)
103
+ recipe_config: Dict[str, Any] = Field(default_factory=dict)
104
+ framework: str = Field("praisonai")
105
+ config: Dict[str, Any] = Field(default_factory=dict)
106
+ webhook_url: Optional[str] = Field(None)
107
+ timeout: int = Field(3600)
108
+ session_id: Optional[str] = Field(None)
109
+ idempotency_key: Optional[str] = Field(None)
110
+ idempotency_scope: str = Field("none")
111
+
112
+ # Progress tracking
113
+ progress_percentage: float = Field(0.0)
114
+ progress_step: Optional[str] = Field(None)
115
+ steps_completed: int = Field(0)
116
+ steps_total: Optional[int] = Field(None)
117
+
118
+ # Attribution
119
+ agent_id: Optional[str] = Field(None)
120
+ run_id: Optional[str] = Field(None)
121
+
122
+ # Timestamps
123
+ created_at: datetime = Field(default_factory=datetime.utcnow)
124
+ started_at: Optional[datetime] = Field(None)
125
+ completed_at: Optional[datetime] = Field(None)
126
+
127
+ # Result
128
+ result: Optional[Any] = Field(None)
129
+ error: Optional[str] = Field(None)
130
+ metrics: Optional[Dict[str, Any]] = Field(None)
131
+
132
+ # Internal
133
+ _cancel_requested: bool = False
134
+
135
+ @property
136
+ def duration_seconds(self) -> Optional[float]:
137
+ """Calculate job duration."""
138
+ if self.started_at is None:
139
+ return None
140
+ end_time = self.completed_at or datetime.utcnow()
141
+ return (end_time - self.started_at).total_seconds()
142
+
143
+ @property
144
+ def is_terminal(self) -> bool:
145
+ """Check if job is in a terminal state."""
146
+ return self.status in (JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED)
147
+
148
+ def to_status_response(self, base_url: str = "") -> JobStatusResponse:
149
+ """Convert to status response."""
150
+ retry_after = None
151
+ if self.status == JobStatus.QUEUED:
152
+ retry_after = 2
153
+ elif self.status == JobStatus.RUNNING:
154
+ retry_after = 5
155
+
156
+ return JobStatusResponse(
157
+ job_id=self.id,
158
+ status=self.status,
159
+ progress=JobProgress(
160
+ percentage=self.progress_percentage,
161
+ current_step=self.progress_step,
162
+ steps_completed=self.steps_completed,
163
+ steps_total=self.steps_total
164
+ ),
165
+ created_at=self.created_at,
166
+ started_at=self.started_at,
167
+ completed_at=self.completed_at,
168
+ agent_id=self.agent_id,
169
+ run_id=self.run_id,
170
+ session_id=self.session_id,
171
+ error=self.error,
172
+ retry_after=retry_after
173
+ )
174
+
175
+ def to_result_response(self) -> JobResultResponse:
176
+ """Convert to result response."""
177
+ return JobResultResponse(
178
+ job_id=self.id,
179
+ status=self.status,
180
+ result=self.result,
181
+ result_url=None, # For large results, would be set
182
+ metrics=self.metrics,
183
+ created_at=self.created_at,
184
+ started_at=self.started_at,
185
+ completed_at=self.completed_at,
186
+ duration_seconds=self.duration_seconds,
187
+ error=self.error
188
+ )
189
+
190
+ def start(self):
191
+ """Mark job as started."""
192
+ self.status = JobStatus.RUNNING
193
+ self.started_at = datetime.utcnow()
194
+
195
+ def succeed(self, result: Any, metrics: Optional[Dict[str, Any]] = None):
196
+ """Mark job as succeeded."""
197
+ self.status = JobStatus.SUCCEEDED
198
+ self.result = result
199
+ self.metrics = metrics
200
+ self.completed_at = datetime.utcnow()
201
+ self.progress_percentage = 100.0
202
+
203
+ def fail(self, error: str):
204
+ """Mark job as failed."""
205
+ self.status = JobStatus.FAILED
206
+ self.error = error
207
+ self.completed_at = datetime.utcnow()
208
+
209
+ def cancel(self):
210
+ """Mark job as cancelled."""
211
+ self.status = JobStatus.CANCELLED
212
+ self.completed_at = datetime.utcnow()
213
+ self._cancel_requested = True
214
+
215
+ def update_progress(
216
+ self,
217
+ percentage: Optional[float] = None,
218
+ step: Optional[str] = None,
219
+ steps_completed: Optional[int] = None,
220
+ steps_total: Optional[int] = None
221
+ ):
222
+ """Update job progress."""
223
+ if percentage is not None:
224
+ self.progress_percentage = min(max(percentage, 0.0), 100.0)
225
+ if step is not None:
226
+ self.progress_step = step
227
+ if steps_completed is not None:
228
+ self.steps_completed = steps_completed
229
+ if steps_total is not None:
230
+ self.steps_total = steps_total
@@ -0,0 +1,314 @@
1
+ """
2
+ FastAPI Router for PraisonAI Async Jobs API.
3
+
4
+ Provides HTTP endpoints for job management.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ from typing import Optional
10
+ from datetime import datetime
11
+
12
+ from fastapi import APIRouter, HTTPException, Header, Request, Query, Response
13
+ from sse_starlette.sse import EventSourceResponse
14
+
15
+ from .models import (
16
+ Job,
17
+ JobStatus,
18
+ JobSubmitRequest,
19
+ JobSubmitResponse,
20
+ JobStatusResponse,
21
+ JobResultResponse,
22
+ JobListResponse
23
+ )
24
+ from .store import JobStore
25
+ from .executor import JobExecutor
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def create_router(store: JobStore, executor: JobExecutor) -> APIRouter:
31
+ """
32
+ Create the jobs API router.
33
+
34
+ Args:
35
+ store: Job storage backend
36
+ executor: Job executor
37
+
38
+ Returns:
39
+ FastAPI router with all endpoints
40
+ """
41
+ router = APIRouter(prefix="/api/v1/runs", tags=["jobs"])
42
+
43
+ @router.post("", response_model=JobSubmitResponse, status_code=202)
44
+ async def submit_job(
45
+ request: Request,
46
+ response: Response,
47
+ body: JobSubmitRequest,
48
+ idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key")
49
+ ):
50
+ """
51
+ Submit a new job for execution.
52
+
53
+ Returns 202 Accepted with job ID and status URLs.
54
+ Headers returned:
55
+ - Location: URL to poll for status
56
+ - Retry-After: Suggested seconds before first poll (default: 2)
57
+
58
+ If an Idempotency-Key header is provided and a job with that key
59
+ already exists, returns the existing job instead of creating a new one.
60
+ """
61
+ # Check idempotency key
62
+ effective_key = idempotency_key or body.idempotency_key
63
+ if effective_key:
64
+ existing = await store.get_by_idempotency_key(effective_key)
65
+ if existing:
66
+ logger.info(f"Returning existing job for idempotency key: {effective_key}")
67
+ base_url = str(request.base_url).rstrip("/")
68
+ poll_url = f"{base_url}/api/v1/runs/{existing.id}"
69
+ response.headers["Location"] = poll_url
70
+ response.headers["Retry-After"] = "2"
71
+ return JobSubmitResponse(
72
+ job_id=existing.id,
73
+ status=existing.status,
74
+ created_at=existing.created_at,
75
+ poll_url=poll_url,
76
+ stream_url=f"{base_url}/api/v1/runs/{existing.id}/stream"
77
+ )
78
+
79
+ # Create new job
80
+ job = Job(
81
+ prompt=body.prompt,
82
+ agent_file=body.agent_file,
83
+ agent_yaml=body.agent_yaml,
84
+ framework=body.framework or "praisonai",
85
+ config=body.config or {},
86
+ webhook_url=body.webhook_url,
87
+ timeout=body.timeout or 3600,
88
+ session_id=body.session_id,
89
+ idempotency_key=effective_key
90
+ )
91
+
92
+ # Submit for execution
93
+ await executor.submit(job)
94
+
95
+ # Build response with Location and Retry-After headers (RFC best practice)
96
+ base_url = str(request.base_url).rstrip("/")
97
+ poll_url = f"{base_url}/api/v1/runs/{job.id}"
98
+ response.headers["Location"] = poll_url
99
+ response.headers["Retry-After"] = "2"
100
+
101
+ return JobSubmitResponse(
102
+ job_id=job.id,
103
+ status=job.status,
104
+ created_at=job.created_at,
105
+ poll_url=poll_url,
106
+ stream_url=f"{base_url}/api/v1/runs/{job.id}/stream"
107
+ )
108
+
109
+ @router.get("", response_model=JobListResponse)
110
+ async def list_jobs(
111
+ status: Optional[str] = Query(None, description="Filter by status"),
112
+ session_id: Optional[str] = Query(None, description="Filter by session ID"),
113
+ page: int = Query(1, ge=1, description="Page number"),
114
+ page_size: int = Query(20, ge=1, le=100, description="Jobs per page")
115
+ ):
116
+ """
117
+ List jobs with optional filters.
118
+ """
119
+ # Parse status filter
120
+ status_filter = None
121
+ if status:
122
+ try:
123
+ status_filter = JobStatus(status)
124
+ except ValueError:
125
+ raise HTTPException(
126
+ status_code=400,
127
+ detail=f"Invalid status: {status}. Valid values: {[s.value for s in JobStatus]}"
128
+ )
129
+
130
+ # Get jobs
131
+ offset = (page - 1) * page_size
132
+ jobs = await store.list_jobs(
133
+ status=status_filter,
134
+ session_id=session_id,
135
+ limit=page_size,
136
+ offset=offset
137
+ )
138
+
139
+ total = await store.count(status=status_filter, session_id=session_id)
140
+
141
+ return JobListResponse(
142
+ jobs=[job.to_status_response() for job in jobs],
143
+ total=total,
144
+ page=page,
145
+ page_size=page_size
146
+ )
147
+
148
+ @router.get("/{job_id}", response_model=JobStatusResponse)
149
+ async def get_job_status(job_id: str):
150
+ """
151
+ Get the status of a job.
152
+
153
+ Returns current status, progress, and timing information.
154
+ """
155
+ job = await store.get(job_id)
156
+ if not job:
157
+ raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
158
+
159
+ return job.to_status_response()
160
+
161
+ @router.get("/{job_id}/result", response_model=JobResultResponse)
162
+ async def get_job_result(job_id: str):
163
+ """
164
+ Get the result of a completed job.
165
+
166
+ Returns 409 Conflict if job is not yet complete.
167
+ """
168
+ job = await store.get(job_id)
169
+ if not job:
170
+ raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
171
+
172
+ if not job.is_terminal:
173
+ raise HTTPException(
174
+ status_code=409,
175
+ detail=f"Job is not complete. Current status: {job.status.value}"
176
+ )
177
+
178
+ return job.to_result_response()
179
+
180
+ @router.post("/{job_id}/cancel", response_model=JobStatusResponse)
181
+ async def cancel_job(job_id: str):
182
+ """
183
+ Cancel a running job.
184
+
185
+ Returns 409 Conflict if job is already complete.
186
+ """
187
+ job = await store.get(job_id)
188
+ if not job:
189
+ raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
190
+
191
+ if job.is_terminal:
192
+ raise HTTPException(
193
+ status_code=409,
194
+ detail=f"Job is already complete. Status: {job.status.value}"
195
+ )
196
+
197
+ success = await executor.cancel(job_id)
198
+ if not success:
199
+ raise HTTPException(status_code=500, detail="Failed to cancel job")
200
+
201
+ # Refresh job state
202
+ job = await store.get(job_id)
203
+ return job.to_status_response()
204
+
205
+ @router.delete("/{job_id}", status_code=204)
206
+ async def delete_job(job_id: str):
207
+ """
208
+ Delete a job.
209
+
210
+ Only completed jobs can be deleted.
211
+ """
212
+ job = await store.get(job_id)
213
+ if not job:
214
+ raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
215
+
216
+ if not job.is_terminal:
217
+ raise HTTPException(
218
+ status_code=409,
219
+ detail="Cannot delete a running job. Cancel it first."
220
+ )
221
+
222
+ await store.delete(job_id)
223
+
224
+ @router.get("/{job_id}/stream")
225
+ async def stream_job(job_id: str):
226
+ """
227
+ Stream job progress via Server-Sent Events (SSE).
228
+
229
+ Events:
230
+ - status: Job status updates
231
+ - progress: Progress percentage updates
232
+ - result: Final result (when complete)
233
+ - error: Error message (when failed)
234
+ """
235
+ job = await store.get(job_id)
236
+ if not job:
237
+ raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
238
+
239
+ async def event_generator():
240
+ """Generate SSE events for job progress."""
241
+ last_status = None
242
+ last_progress = -1
243
+
244
+ # Register for progress updates
245
+ progress_queue = asyncio.Queue()
246
+
247
+ async def on_progress(updated_job: Job):
248
+ await progress_queue.put(updated_job)
249
+
250
+ executor.register_progress_callback(job_id, on_progress)
251
+
252
+ try:
253
+ while True:
254
+ # Get current job state
255
+ current_job = await store.get(job_id)
256
+ if not current_job:
257
+ yield {
258
+ "event": "error",
259
+ "data": '{"error": "Job not found"}'
260
+ }
261
+ break
262
+
263
+ # Send status update if changed
264
+ if current_job.status != last_status:
265
+ last_status = current_job.status
266
+ yield {
267
+ "event": "status",
268
+ "data": f'{{"status": "{current_job.status.value}", "job_id": "{job_id}"}}'
269
+ }
270
+
271
+ # Send progress update if changed
272
+ if current_job.progress_percentage != last_progress:
273
+ last_progress = current_job.progress_percentage
274
+ yield {
275
+ "event": "progress",
276
+ "data": f'{{"percentage": {current_job.progress_percentage}, "step": "{current_job.progress_step or ""}"}}'
277
+ }
278
+
279
+ # Check if complete
280
+ if current_job.is_terminal:
281
+ if current_job.status == JobStatus.SUCCEEDED:
282
+ result_str = str(current_job.result).replace('"', '\\"')[:1000]
283
+ yield {
284
+ "event": "result",
285
+ "data": f'{{"result": "{result_str}"}}'
286
+ }
287
+ elif current_job.status == JobStatus.FAILED:
288
+ error_str = (current_job.error or "Unknown error").replace('"', '\\"')
289
+ yield {
290
+ "event": "error",
291
+ "data": f'{{"error": "{error_str}"}}'
292
+ }
293
+ elif current_job.status == JobStatus.CANCELLED:
294
+ yield {
295
+ "event": "cancelled",
296
+ "data": '{"message": "Job was cancelled"}'
297
+ }
298
+ break
299
+
300
+ # Wait for next update or timeout
301
+ try:
302
+ await asyncio.wait_for(progress_queue.get(), timeout=5.0)
303
+ except asyncio.TimeoutError:
304
+ # Send heartbeat
305
+ yield {
306
+ "event": "heartbeat",
307
+ "data": f'{{"timestamp": "{datetime.utcnow().isoformat()}"}}'
308
+ }
309
+ finally:
310
+ executor.unregister_progress_callback(job_id)
311
+
312
+ return EventSourceResponse(event_generator())
313
+
314
+ return router