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,564 @@
1
+ """
2
+ Template Loader
3
+
4
+ Loads and materializes templates into Agent/Workflow configurations.
5
+ Handles TEMPLATE.yaml parsing, config merging, and skills integration.
6
+ """
7
+
8
+ import os
9
+ import re
10
+ import secrets
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, Tuple
13
+ from dataclasses import dataclass, field
14
+
15
+ from .resolver import ResolvedTemplate # noqa: F401 - used by registry
16
+ from .cache import TemplateCache
17
+ from .registry import TemplateRegistry
18
+ from .security import TemplateSecurity
19
+
20
+
21
+ @dataclass
22
+ class TemplateConfig:
23
+ """Parsed template configuration."""
24
+ name: str
25
+ description: str = ""
26
+ version: str = "1.0.0"
27
+ author: Optional[str] = None
28
+ license: Optional[str] = None
29
+ tags: List[str] = field(default_factory=list)
30
+
31
+ # Dependencies
32
+ requires: Dict[str, Any] = field(default_factory=dict)
33
+
34
+ # Entry points
35
+ workflow_file: str = "workflow.yaml"
36
+ agents_file: str = "agents.yaml"
37
+
38
+ # Configuration schema
39
+ config_schema: Dict[str, Any] = field(default_factory=dict)
40
+
41
+ # Default configuration values
42
+ defaults: Dict[str, Any] = field(default_factory=dict)
43
+
44
+ # Skills to load
45
+ skills: List[str] = field(default_factory=list)
46
+
47
+ # CLI integration
48
+ cli: Dict[str, Any] = field(default_factory=dict)
49
+
50
+ # Runtime configuration (background/job/schedule)
51
+ runtime: Optional[Any] = None # RuntimeConfig, lazy loaded
52
+
53
+ # Raw config dict
54
+ raw: Dict[str, Any] = field(default_factory=dict)
55
+
56
+ # Source path
57
+ path: Optional[Path] = None
58
+
59
+
60
+ class TemplateLoader:
61
+ """
62
+ Loads templates and materializes them into usable configurations.
63
+
64
+ Supports:
65
+ - TEMPLATE.yaml parsing
66
+ - Config override merging
67
+ - Skills integration
68
+ - Workflow/Agent file loading
69
+ """
70
+
71
+ TEMPLATE_FILE = "TEMPLATE.yaml"
72
+
73
+ def __init__(
74
+ self,
75
+ cache: Optional[TemplateCache] = None,
76
+ registry: Optional[TemplateRegistry] = None,
77
+ security: Optional[TemplateSecurity] = None,
78
+ offline: bool = False
79
+ ):
80
+ """
81
+ Initialize the loader.
82
+
83
+ Args:
84
+ cache: Template cache instance
85
+ registry: Template registry instance
86
+ security: Security handler instance
87
+ offline: If True, only use cached templates
88
+ """
89
+ self.cache = cache or TemplateCache()
90
+ self.registry = registry or TemplateRegistry(cache=self.cache, offline=offline)
91
+ self.security = security or TemplateSecurity()
92
+ self.offline = offline
93
+
94
+ def load(
95
+ self,
96
+ uri: str,
97
+ config: Optional[Dict[str, Any]] = None,
98
+ offline: bool = False
99
+ ) -> TemplateConfig:
100
+ """
101
+ Load a template by URI.
102
+
103
+ Args:
104
+ uri: Template URI
105
+ config: Optional config overrides
106
+ offline: If True, only use cache
107
+
108
+ Returns:
109
+ TemplateConfig with parsed configuration
110
+
111
+ Raises:
112
+ ValueError: If template not found or invalid
113
+ SecurityError: If template fails security checks
114
+ """
115
+ # Security check
116
+ if not self.security.is_source_allowed(uri):
117
+ raise ValueError(f"Template source not allowed: {uri}")
118
+
119
+ # Get template (from cache or remote)
120
+ use_offline = offline or self.offline
121
+ cached = self.registry.get_template(uri, offline=use_offline)
122
+
123
+ # Validate security
124
+ errors = self.security.validate_template_directory(cached.path)
125
+ if errors:
126
+ raise ValueError("Template security validation failed:\n" + "\n".join(errors))
127
+
128
+ # Parse TEMPLATE.yaml
129
+ template_config = self._parse_template_file(cached.path)
130
+ template_config.path = cached.path
131
+
132
+ # Merge config overrides
133
+ if config:
134
+ template_config = self._merge_config(template_config, config)
135
+
136
+ return template_config
137
+
138
+ def _parse_template_file(self, template_dir: Path) -> TemplateConfig:
139
+ """Parse TEMPLATE.yaml file."""
140
+ import yaml
141
+
142
+ template_file = template_dir / self.TEMPLATE_FILE
143
+
144
+ if not template_file.exists():
145
+ # Try to infer from directory name
146
+ return TemplateConfig(
147
+ name=template_dir.name,
148
+ path=template_dir
149
+ )
150
+
151
+ with open(template_file) as f:
152
+ raw = yaml.safe_load(f) or {}
153
+
154
+ # Sanitize config
155
+ raw = self.security.sanitize_template_config(raw)
156
+
157
+ # Extract requires
158
+ requires = raw.get("requires", {})
159
+ if isinstance(requires, list):
160
+ requires = {"tools": requires}
161
+
162
+ # Handle workflow - can be inline dict or file reference string
163
+ workflow = raw.get("workflow", "workflow.yaml")
164
+
165
+ # Handle agents - can be inline list or file reference string
166
+ agents = raw.get("agents", "agents.yaml")
167
+
168
+ # Parse runtime configuration (lazy import to avoid circular deps)
169
+ runtime = None
170
+ if "runtime" in raw:
171
+ from praisonai.recipe.runtime import parse_runtime_config
172
+ runtime = parse_runtime_config(raw.get("runtime"), expand_env=True)
173
+
174
+ return TemplateConfig(
175
+ name=raw.get("name", template_dir.name),
176
+ description=raw.get("description", ""),
177
+ version=raw.get("version", "1.0.0"),
178
+ author=raw.get("author"),
179
+ license=raw.get("license"),
180
+ tags=raw.get("tags", []),
181
+ requires=requires,
182
+ workflow_file=workflow,
183
+ agents_file=agents,
184
+ config_schema=raw.get("config", {}),
185
+ defaults=raw.get("defaults", {}),
186
+ skills=raw.get("skills", []),
187
+ cli=raw.get("cli", {}),
188
+ runtime=runtime,
189
+ raw=raw,
190
+ path=template_dir
191
+ )
192
+
193
+ def _merge_config(
194
+ self,
195
+ template: TemplateConfig,
196
+ overrides: Dict[str, Any]
197
+ ) -> TemplateConfig:
198
+ """Merge config overrides into template config."""
199
+ # Start with defaults
200
+ merged = dict(template.defaults)
201
+
202
+ # Apply overrides
203
+ merged.update(overrides)
204
+
205
+ # Update template
206
+ template.defaults = merged
207
+
208
+ return template
209
+
210
+ def load_workflow_config(
211
+ self,
212
+ template: TemplateConfig
213
+ ) -> Dict[str, Any]:
214
+ """
215
+ Load workflow configuration from template.
216
+
217
+ Args:
218
+ template: Parsed template config
219
+
220
+ Returns:
221
+ Workflow configuration dict
222
+
223
+ Supports:
224
+ - Inline workflow definition in TEMPLATE.yaml (workflow: {agents: [...], tasks: [...]})
225
+ - Separate workflow file reference (workflow: "workflow.yaml")
226
+ """
227
+ import yaml
228
+
229
+ # Check if workflow is inline (dict) or a file reference (string)
230
+ if isinstance(template.workflow_file, dict):
231
+ # Inline workflow definition
232
+ config = template.workflow_file
233
+ elif isinstance(template.workflow_file, str):
234
+ # File reference
235
+ workflow_file = template.path / template.workflow_file
236
+
237
+ if not workflow_file.exists():
238
+ # Check if workflow is in raw config
239
+ if "workflow" in template.raw and isinstance(template.raw["workflow"], dict):
240
+ config = template.raw["workflow"]
241
+ else:
242
+ raise ValueError(f"Workflow file not found: {workflow_file}")
243
+ else:
244
+ with open(workflow_file) as f:
245
+ config = yaml.safe_load(f) or {}
246
+ else:
247
+ raise ValueError(f"Invalid workflow configuration type: {type(template.workflow_file)}")
248
+
249
+ # Substitute variables from template config
250
+ config = self._substitute_variables(config, template.defaults)
251
+
252
+ return config
253
+
254
+ def load_agents_config(
255
+ self,
256
+ template: TemplateConfig
257
+ ) -> Dict[str, Any]:
258
+ """
259
+ Load agents configuration from template.
260
+
261
+ Args:
262
+ template: Parsed template config
263
+
264
+ Returns:
265
+ Agents configuration dict
266
+ """
267
+ import yaml
268
+
269
+ agents_file = template.path / template.agents_file
270
+
271
+ if not agents_file.exists():
272
+ # Try workflow file for inline agents
273
+ return {}
274
+
275
+ with open(agents_file) as f:
276
+ config = yaml.safe_load(f) or {}
277
+
278
+ # Substitute variables
279
+ config = self._substitute_variables(config, template.defaults)
280
+
281
+ return config
282
+
283
+ def _substitute_variables(
284
+ self,
285
+ config: Any,
286
+ variables: Dict[str, Any]
287
+ ) -> Any:
288
+ """
289
+ Recursively substitute variables in config with sentinel protection.
290
+
291
+ This method implements a secure multi-phase substitution strategy:
292
+ 1. Generate a unique sentinel token for this render
293
+ 2. Protect user-provided variable values that contain {{...}} syntax
294
+ 3. Perform template variable substitution on the config
295
+ 4. Restore protected values
296
+
297
+ This prevents user input containing {{variable}} syntax from being
298
+ interpreted as template variables (injection protection).
299
+ """
300
+ if isinstance(config, str):
301
+ # Generate per-render unique sentinel to prevent collision attacks
302
+ sentinel_id = secrets.token_hex(8)
303
+ sentinel_prefix = f"__PRAISONAI_SENTINEL_{sentinel_id}_"
304
+
305
+ # Phase 1: Protect variable values that contain template syntax
306
+ protected_values, safe_variables = self._protect_variable_values(
307
+ variables, sentinel_prefix
308
+ )
309
+
310
+ # Phase 2: Perform substitution with safe values
311
+ pattern = r'\{\{(\w+)\}\}'
312
+
313
+ def replace(match):
314
+ var_name = match.group(1)
315
+ return str(safe_variables.get(var_name, match.group(0)))
316
+
317
+ result = re.sub(pattern, replace, config)
318
+
319
+ # Phase 3: Restore protected values
320
+ result = self._restore_protected_values(result, protected_values)
321
+
322
+ return result
323
+
324
+ elif isinstance(config, dict):
325
+ return {k: self._substitute_variables(v, variables) for k, v in config.items()}
326
+
327
+ elif isinstance(config, list):
328
+ return [self._substitute_variables(item, variables) for item in config]
329
+
330
+ return config
331
+
332
+ def _protect_variable_values(
333
+ self,
334
+ variables: Dict[str, Any],
335
+ sentinel_prefix: str
336
+ ) -> Tuple[Dict[str, str], Dict[str, Any]]:
337
+ """
338
+ Protect variable values that contain template syntax.
339
+
340
+ Args:
341
+ variables: Original variable dict
342
+ sentinel_prefix: Unique prefix for this render
343
+
344
+ Returns:
345
+ Tuple of (protected_values mapping, safe_variables dict)
346
+ """
347
+ protected_values = {} # sentinel -> original value
348
+ safe_variables = {}
349
+
350
+ template_pattern = re.compile(r'\{\{.*?\}\}')
351
+
352
+ for key, value in variables.items():
353
+ if isinstance(value, str) and template_pattern.search(value):
354
+ # This value contains template syntax - protect it
355
+ sentinel = f"{sentinel_prefix}{key}__"
356
+ protected_values[sentinel] = value
357
+ safe_variables[key] = sentinel
358
+ else:
359
+ safe_variables[key] = value
360
+
361
+ return protected_values, safe_variables
362
+
363
+ def _restore_protected_values(
364
+ self,
365
+ content: str,
366
+ protected_values: Dict[str, str]
367
+ ) -> str:
368
+ """
369
+ Restore protected values after substitution.
370
+
371
+ Args:
372
+ content: Content with sentinel tokens
373
+ protected_values: Mapping of sentinel -> original value
374
+
375
+ Returns:
376
+ Content with sentinels replaced by original values
377
+ """
378
+ for sentinel, original in protected_values.items():
379
+ content = content.replace(sentinel, original)
380
+ return content
381
+
382
+ def get_required_tools(self, template: TemplateConfig) -> List[str]:
383
+ """Get list of required tools for a template."""
384
+ tools = template.requires.get("tools", [])
385
+ if isinstance(tools, str):
386
+ tools = [tools]
387
+ return tools
388
+
389
+ def get_required_packages(self, template: TemplateConfig) -> List[str]:
390
+ """Get list of required Python packages for a template."""
391
+ packages = template.requires.get("packages", [])
392
+ if isinstance(packages, str):
393
+ packages = [packages]
394
+ return packages
395
+
396
+ def get_required_env(self, template: TemplateConfig) -> List[str]:
397
+ """Get list of required environment variables for a template."""
398
+ env = template.requires.get("env", [])
399
+ if isinstance(env, str):
400
+ env = [env]
401
+ return env
402
+
403
+ def check_requirements(
404
+ self,
405
+ template: TemplateConfig
406
+ ) -> Dict[str, List[str]]:
407
+ """
408
+ Check if template requirements are satisfied.
409
+
410
+ Returns:
411
+ Dict with 'missing_packages', 'missing_env', 'missing_tools' lists
412
+ """
413
+ result = {
414
+ "missing_packages": [],
415
+ "missing_env": [],
416
+ "missing_tools": []
417
+ }
418
+
419
+ # Check packages
420
+ for package in self.get_required_packages(template):
421
+ try:
422
+ __import__(package.replace("-", "_"))
423
+ except ImportError:
424
+ result["missing_packages"].append(package)
425
+
426
+ # Check environment variables
427
+ for env_var in self.get_required_env(template):
428
+ if not os.environ.get(env_var):
429
+ result["missing_env"].append(env_var)
430
+
431
+ return result
432
+
433
+
434
+ def load_template(
435
+ uri: str,
436
+ config: Optional[Dict[str, Any]] = None,
437
+ offline: bool = False
438
+ ) -> TemplateConfig:
439
+ """
440
+ Convenience function to load a template.
441
+
442
+ Args:
443
+ uri: Template URI
444
+ config: Optional config overrides
445
+ offline: If True, only use cache
446
+
447
+ Returns:
448
+ TemplateConfig with parsed configuration
449
+ """
450
+ loader = TemplateLoader(offline=offline)
451
+ return loader.load(uri, config=config, offline=offline)
452
+
453
+
454
+ def create_agent_from_template(
455
+ uri: str,
456
+ config: Optional[Dict[str, Any]] = None,
457
+ offline: bool = False,
458
+ **agent_kwargs
459
+ ):
460
+ """
461
+ Create an Agent from a template.
462
+
463
+ Args:
464
+ uri: Template URI
465
+ config: Optional config overrides
466
+ offline: If True, only use cache
467
+ **agent_kwargs: Additional Agent constructor arguments
468
+
469
+ Returns:
470
+ Configured Agent instance
471
+ """
472
+ loader = TemplateLoader(offline=offline)
473
+ template = loader.load(uri, config=config, offline=offline)
474
+
475
+ # Load agents config
476
+ agents_config = loader.load_agents_config(template)
477
+
478
+ # Get first agent or use template defaults
479
+ if agents_config and "agents" in agents_config:
480
+ agent_list = list(agents_config["agents"].values())
481
+ if agent_list:
482
+ agent_config = agent_list[0]
483
+ else:
484
+ agent_config = {}
485
+ else:
486
+ agent_config = {}
487
+
488
+ # Merge with kwargs
489
+ final_config = {**agent_config, **agent_kwargs}
490
+
491
+ # Import Agent lazily
492
+ from praisonaiagents import Agent
493
+
494
+ # Handle skills
495
+ if template.skills:
496
+ final_config["skills"] = template.skills
497
+
498
+ return Agent(**final_config)
499
+
500
+
501
+ def create_workflow_from_template(
502
+ uri: str,
503
+ config: Optional[Dict[str, Any]] = None,
504
+ offline: bool = False,
505
+ **workflow_kwargs
506
+ ):
507
+ """
508
+ Create a Workflow from a template.
509
+
510
+ Args:
511
+ uri: Template URI
512
+ config: Optional config overrides
513
+ offline: If True, only use cache
514
+ **workflow_kwargs: Additional Workflow constructor arguments
515
+
516
+ Returns:
517
+ Configured Workflow instance
518
+ """
519
+ loader = TemplateLoader(offline=offline)
520
+ template = loader.load(uri, config=config, offline=offline)
521
+
522
+ # Load workflow config
523
+ workflow_config = loader.load_workflow_config(template)
524
+
525
+ # Merge with kwargs
526
+ final_config = {**workflow_config, **workflow_kwargs}
527
+
528
+ # Import Workflow lazily
529
+ from praisonaiagents import Workflow
530
+
531
+ return Workflow(**final_config)
532
+
533
+
534
+ def create_agents_from_template(
535
+ uri: str,
536
+ config: Optional[Dict[str, Any]] = None,
537
+ offline: bool = False,
538
+ **agents_kwargs
539
+ ):
540
+ """
541
+ Create a PraisonAIAgents team from a template.
542
+
543
+ Args:
544
+ uri: Template URI
545
+ config: Optional config overrides
546
+ offline: If True, only use cache
547
+ **agents_kwargs: Additional PraisonAIAgents constructor arguments
548
+
549
+ Returns:
550
+ Configured PraisonAIAgents instance
551
+ """
552
+ loader = TemplateLoader(offline=offline)
553
+ template = loader.load(uri, config=config, offline=offline)
554
+
555
+ # Load workflow config (contains agents and tasks)
556
+ workflow_config = loader.load_workflow_config(template)
557
+
558
+ # Import PraisonAIAgents lazily
559
+ from praisonaiagents import PraisonAIAgents
560
+
561
+ # Merge with kwargs
562
+ final_config = {**workflow_config, **agents_kwargs}
563
+
564
+ return PraisonAIAgents(**final_config)