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,652 @@
1
+ """
2
+ Performance Optimizations for PraisonAI CLI.
3
+
4
+ Implements Tier 0/1/2 optimizations:
5
+ - Tier 0: Lazy imports, provider caching, CLI startup optimization
6
+ - Tier 1: Connection pooling, prewarm hooks (opt-in)
7
+ - Tier 2: Lite mode, async init, perf snapshot
8
+
9
+ All optimizations are safe-by-default and opt-in where behavior changes.
10
+ Multi-agent safe: no global mutable state that affects concurrent runs.
11
+ """
12
+
13
+ import os
14
+ import json
15
+ import threading
16
+ import time
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any, Callable, Dict, List, Optional, Tuple
20
+ from functools import lru_cache
21
+
22
+
23
+ # =============================================================================
24
+ # TIER 0: Safe Fast Wins (No behavior change)
25
+ # =============================================================================
26
+
27
+ class ProviderCache:
28
+ """
29
+ Thread-safe cache for provider/model resolution.
30
+
31
+ Caches resolved provider configurations to avoid repeated lookups.
32
+ Per-process cache with clear invalidation rules.
33
+ Multi-agent safe: uses thread-local storage for mutable state.
34
+ """
35
+
36
+ _instance = None
37
+ _lock = threading.Lock()
38
+
39
+ def __new__(cls):
40
+ if cls._instance is None:
41
+ with cls._lock:
42
+ if cls._instance is None:
43
+ cls._instance = super().__new__(cls)
44
+ cls._instance._cache = {}
45
+ cls._instance._cache_lock = threading.Lock()
46
+ cls._instance._hits = 0
47
+ cls._instance._misses = 0
48
+ return cls._instance
49
+
50
+ def get(self, key: str) -> Optional[Any]:
51
+ """Get cached value (thread-safe)."""
52
+ with self._cache_lock:
53
+ if key in self._cache:
54
+ self._hits += 1
55
+ return self._cache[key]
56
+ self._misses += 1
57
+ return None
58
+
59
+ def set(self, key: str, value: Any, ttl_seconds: int = 300) -> None:
60
+ """Set cached value with TTL (thread-safe)."""
61
+ with self._cache_lock:
62
+ self._cache[key] = {
63
+ 'value': value,
64
+ 'expires': time.time() + ttl_seconds,
65
+ }
66
+
67
+ def invalidate(self, key: Optional[str] = None) -> None:
68
+ """Invalidate cache entry or entire cache."""
69
+ with self._cache_lock:
70
+ if key:
71
+ self._cache.pop(key, None)
72
+ else:
73
+ self._cache.clear()
74
+
75
+ def get_stats(self) -> Dict[str, int]:
76
+ """Get cache statistics."""
77
+ with self._cache_lock:
78
+ return {
79
+ 'hits': self._hits,
80
+ 'misses': self._misses,
81
+ 'size': len(self._cache),
82
+ }
83
+
84
+ def cleanup_expired(self) -> int:
85
+ """Remove expired entries. Returns count of removed entries."""
86
+ now = time.time()
87
+ removed = 0
88
+ with self._cache_lock:
89
+ expired_keys = [
90
+ k for k, v in self._cache.items()
91
+ if isinstance(v, dict) and v.get('expires', float('inf')) < now
92
+ ]
93
+ for k in expired_keys:
94
+ del self._cache[k]
95
+ removed += 1
96
+ return removed
97
+
98
+
99
+ # Global provider cache instance (singleton, thread-safe)
100
+ _provider_cache = None
101
+
102
+ def get_provider_cache() -> ProviderCache:
103
+ """Get the global provider cache instance."""
104
+ global _provider_cache
105
+ if _provider_cache is None:
106
+ _provider_cache = ProviderCache()
107
+ return _provider_cache
108
+
109
+
110
+ @lru_cache(maxsize=32)
111
+ def resolve_model_provider(model: str) -> Tuple[str, str]:
112
+ """
113
+ Resolve model string to (provider, model_name) tuple.
114
+
115
+ Cached using LRU cache for repeated lookups.
116
+ """
117
+ if '/' in model:
118
+ parts = model.split('/', 1)
119
+ return parts[0], parts[1]
120
+
121
+ # Default provider mappings
122
+ if model.startswith('gpt-') or model.startswith('o1-') or model.startswith('o3-'):
123
+ return 'openai', model
124
+ elif model.startswith('claude-'):
125
+ return 'anthropic', model
126
+ elif model.startswith('gemini-'):
127
+ return 'google', model
128
+ elif model.startswith('llama') or model.startswith('mistral'):
129
+ return 'ollama', model
130
+
131
+ return 'openai', model # Default to OpenAI
132
+
133
+
134
+ class LazyImporter:
135
+ """
136
+ Lazy importer for heavy modules.
137
+
138
+ Defers import until first use, reducing CLI startup time.
139
+ Thread-safe and caches imported modules.
140
+ """
141
+
142
+ _imports: Dict[str, Any] = {}
143
+ _lock = threading.Lock()
144
+
145
+ @classmethod
146
+ def get(cls, module_path: str, attr: Optional[str] = None) -> Any:
147
+ """
148
+ Lazily import a module or attribute.
149
+
150
+ Args:
151
+ module_path: Full module path (e.g., 'openai.OpenAI')
152
+ attr: Optional attribute to get from module
153
+
154
+ Returns:
155
+ Imported module or attribute
156
+ """
157
+ cache_key = f"{module_path}.{attr}" if attr else module_path
158
+
159
+ with cls._lock:
160
+ if cache_key in cls._imports:
161
+ return cls._imports[cache_key]
162
+
163
+ # Import outside lock to avoid deadlocks
164
+ import importlib
165
+
166
+ if attr:
167
+ module = importlib.import_module(module_path)
168
+ result = getattr(module, attr)
169
+ else:
170
+ result = importlib.import_module(module_path)
171
+
172
+ with cls._lock:
173
+ cls._imports[cache_key] = result
174
+
175
+ return result
176
+
177
+ @classmethod
178
+ def clear(cls) -> None:
179
+ """Clear import cache."""
180
+ with cls._lock:
181
+ cls._imports.clear()
182
+
183
+
184
+ # =============================================================================
185
+ # TIER 1: Medium Effort (Guarded/opt-in)
186
+ # =============================================================================
187
+
188
+ class ClientPool:
189
+ """
190
+ Connection pool for API clients.
191
+
192
+ Reuses client instances for repeated calls to the same provider.
193
+ Multi-agent safe: clients are isolated by key.
194
+ Does not leak API keys between different configurations.
195
+ """
196
+
197
+ def __init__(self, max_size: int = 10):
198
+ self._pool: Dict[str, Any] = {}
199
+ self._lock = threading.Lock()
200
+ self._max_size = max_size
201
+ self._access_times: Dict[str, float] = {}
202
+
203
+ def get_or_create(
204
+ self,
205
+ key: str,
206
+ factory: Callable[[], Any],
207
+ ) -> Any:
208
+ """
209
+ Get existing client or create new one.
210
+
211
+ Args:
212
+ key: Unique key for this client configuration
213
+ factory: Function to create new client if needed
214
+
215
+ Returns:
216
+ Client instance
217
+ """
218
+ with self._lock:
219
+ if key in self._pool:
220
+ self._access_times[key] = time.time()
221
+ return self._pool[key]
222
+
223
+ # Evict oldest if at capacity
224
+ if len(self._pool) >= self._max_size:
225
+ oldest_key = min(self._access_times, key=self._access_times.get)
226
+ del self._pool[oldest_key]
227
+ del self._access_times[oldest_key]
228
+
229
+ # Create outside lock
230
+ client = factory()
231
+
232
+ with self._lock:
233
+ self._pool[key] = client
234
+ self._access_times[key] = time.time()
235
+
236
+ return client
237
+
238
+ def remove(self, key: str) -> None:
239
+ """Remove client from pool."""
240
+ with self._lock:
241
+ self._pool.pop(key, None)
242
+ self._access_times.pop(key, None)
243
+
244
+ def clear(self) -> None:
245
+ """Clear all clients from pool."""
246
+ with self._lock:
247
+ self._pool.clear()
248
+ self._access_times.clear()
249
+
250
+ def size(self) -> int:
251
+ """Get current pool size."""
252
+ with self._lock:
253
+ return len(self._pool)
254
+
255
+
256
+ # Global client pool (opt-in usage)
257
+ _client_pool: Optional[ClientPool] = None
258
+
259
+ def get_client_pool() -> ClientPool:
260
+ """Get the global client pool instance."""
261
+ global _client_pool
262
+ if _client_pool is None:
263
+ _client_pool = ClientPool()
264
+ return _client_pool
265
+
266
+
267
+ class PrewarmManager:
268
+ """
269
+ Manager for pre-warming provider connections.
270
+
271
+ OPT-IN ONLY: Must be explicitly enabled.
272
+ Pre-initializes provider clients in background to reduce first-call latency.
273
+ """
274
+
275
+ _enabled = False
276
+ _prewarmed: Dict[str, bool] = {}
277
+ _lock = threading.Lock()
278
+ _background_thread: Optional[threading.Thread] = None
279
+
280
+ @classmethod
281
+ def enable(cls) -> None:
282
+ """Enable pre-warming (opt-in)."""
283
+ cls._enabled = True
284
+
285
+ @classmethod
286
+ def disable(cls) -> None:
287
+ """Disable pre-warming."""
288
+ cls._enabled = False
289
+
290
+ @classmethod
291
+ def is_enabled(cls) -> bool:
292
+ """Check if pre-warming is enabled."""
293
+ return cls._enabled
294
+
295
+ @classmethod
296
+ def prewarm_provider(cls, provider: str, api_key: Optional[str] = None) -> None:
297
+ """
298
+ Pre-warm a provider connection in background.
299
+
300
+ Only runs if pre-warming is enabled.
301
+ """
302
+ if not cls._enabled:
303
+ return
304
+
305
+ with cls._lock:
306
+ if provider in cls._prewarmed:
307
+ return
308
+ cls._prewarmed[provider] = False
309
+
310
+ def _prewarm():
311
+ try:
312
+ if provider == 'openai':
313
+ # Just import and create client - don't make API call
314
+ from openai import OpenAI
315
+ key = api_key or os.environ.get('OPENAI_API_KEY')
316
+ if key:
317
+ _ = OpenAI(api_key=key)
318
+ elif provider == 'anthropic':
319
+ from anthropic import Anthropic
320
+ key = api_key or os.environ.get('ANTHROPIC_API_KEY')
321
+ if key:
322
+ _ = Anthropic(api_key=key)
323
+
324
+ with cls._lock:
325
+ cls._prewarmed[provider] = True
326
+ except Exception:
327
+ pass # Silently fail - pre-warming is optional
328
+
329
+ # Run in background thread
330
+ thread = threading.Thread(target=_prewarm, daemon=True)
331
+ thread.start()
332
+
333
+ @classmethod
334
+ def is_prewarmed(cls, provider: str) -> bool:
335
+ """Check if provider is pre-warmed."""
336
+ with cls._lock:
337
+ return cls._prewarmed.get(provider, False)
338
+
339
+ @classmethod
340
+ def reset(cls) -> None:
341
+ """Reset pre-warm state."""
342
+ with cls._lock:
343
+ cls._prewarmed.clear()
344
+ cls._enabled = False
345
+
346
+
347
+ # =============================================================================
348
+ # TIER 2: Architectural (Safe and non-breaking)
349
+ # =============================================================================
350
+
351
+ @dataclass
352
+ class LiteModeConfig:
353
+ """
354
+ Configuration for lite runtime mode.
355
+
356
+ OPT-IN ONLY: Defaults to OFF.
357
+ Avoids expensive type/model loading when not required.
358
+ """
359
+ enabled: bool = False
360
+ skip_type_validation: bool = False
361
+ skip_model_validation: bool = False
362
+ minimal_imports: bool = False
363
+
364
+ @classmethod
365
+ def from_env(cls) -> 'LiteModeConfig':
366
+ """Create config from environment variables."""
367
+ return cls(
368
+ enabled=os.environ.get('PRAISONAI_LITE_MODE', '').lower() in ('1', 'true', 'yes'),
369
+ skip_type_validation=os.environ.get('PRAISONAI_SKIP_TYPE_VALIDATION', '').lower() in ('1', 'true'),
370
+ skip_model_validation=os.environ.get('PRAISONAI_SKIP_MODEL_VALIDATION', '').lower() in ('1', 'true'),
371
+ minimal_imports=os.environ.get('PRAISONAI_MINIMAL_IMPORTS', '').lower() in ('1', 'true'),
372
+ )
373
+
374
+
375
+ # Global lite mode config
376
+ _lite_mode_config: Optional[LiteModeConfig] = None
377
+
378
+ def get_lite_mode_config() -> LiteModeConfig:
379
+ """Get lite mode configuration."""
380
+ global _lite_mode_config
381
+ if _lite_mode_config is None:
382
+ _lite_mode_config = LiteModeConfig.from_env()
383
+ return _lite_mode_config
384
+
385
+ def is_lite_mode() -> bool:
386
+ """Check if lite mode is enabled."""
387
+ return get_lite_mode_config().enabled
388
+
389
+
390
+ @dataclass
391
+ class PerfSnapshot:
392
+ """
393
+ Performance snapshot for baseline comparison.
394
+
395
+ Records timing data that can be compared against later runs.
396
+ """
397
+ timestamp: str
398
+ name: str
399
+ startup_cold_ms: float
400
+ startup_warm_ms: float
401
+ import_time_ms: float
402
+ query_time_ms: float
403
+ first_token_ms: float
404
+ metadata: Dict[str, Any] = field(default_factory=dict)
405
+
406
+ def to_dict(self) -> Dict[str, Any]:
407
+ return {
408
+ 'timestamp': self.timestamp,
409
+ 'name': self.name,
410
+ 'startup_cold_ms': self.startup_cold_ms,
411
+ 'startup_warm_ms': self.startup_warm_ms,
412
+ 'import_time_ms': self.import_time_ms,
413
+ 'query_time_ms': self.query_time_ms,
414
+ 'first_token_ms': self.first_token_ms,
415
+ 'metadata': self.metadata,
416
+ }
417
+
418
+ @classmethod
419
+ def from_dict(cls, data: Dict[str, Any]) -> 'PerfSnapshot':
420
+ return cls(
421
+ timestamp=data['timestamp'],
422
+ name=data['name'],
423
+ startup_cold_ms=data['startup_cold_ms'],
424
+ startup_warm_ms=data['startup_warm_ms'],
425
+ import_time_ms=data['import_time_ms'],
426
+ query_time_ms=data['query_time_ms'],
427
+ first_token_ms=data.get('first_token_ms', 0.0),
428
+ metadata=data.get('metadata', {}),
429
+ )
430
+
431
+
432
+ @dataclass
433
+ class PerfComparison:
434
+ """Comparison between two performance snapshots."""
435
+ baseline: PerfSnapshot
436
+ current: PerfSnapshot
437
+
438
+ @property
439
+ def startup_cold_diff_ms(self) -> float:
440
+ return self.current.startup_cold_ms - self.baseline.startup_cold_ms
441
+
442
+ @property
443
+ def startup_cold_diff_pct(self) -> float:
444
+ if self.baseline.startup_cold_ms == 0:
445
+ return 0.0
446
+ return (self.startup_cold_diff_ms / self.baseline.startup_cold_ms) * 100
447
+
448
+ @property
449
+ def import_time_diff_ms(self) -> float:
450
+ return self.current.import_time_ms - self.baseline.import_time_ms
451
+
452
+ @property
453
+ def import_time_diff_pct(self) -> float:
454
+ if self.baseline.import_time_ms == 0:
455
+ return 0.0
456
+ return (self.import_time_diff_ms / self.baseline.import_time_ms) * 100
457
+
458
+ @property
459
+ def query_time_diff_ms(self) -> float:
460
+ return self.current.query_time_ms - self.baseline.query_time_ms
461
+
462
+ @property
463
+ def query_time_diff_pct(self) -> float:
464
+ if self.baseline.query_time_ms == 0:
465
+ return 0.0
466
+ return (self.query_time_diff_ms / self.baseline.query_time_ms) * 100
467
+
468
+ def is_regression(self, threshold_pct: float = 10.0) -> bool:
469
+ """Check if there's a performance regression above threshold."""
470
+ return (
471
+ self.startup_cold_diff_pct > threshold_pct or
472
+ self.import_time_diff_pct > threshold_pct or
473
+ self.query_time_diff_pct > threshold_pct
474
+ )
475
+
476
+ def to_dict(self) -> Dict[str, Any]:
477
+ return {
478
+ 'baseline': self.baseline.to_dict(),
479
+ 'current': self.current.to_dict(),
480
+ 'diffs': {
481
+ 'startup_cold_ms': self.startup_cold_diff_ms,
482
+ 'startup_cold_pct': self.startup_cold_diff_pct,
483
+ 'import_time_ms': self.import_time_diff_ms,
484
+ 'import_time_pct': self.import_time_diff_pct,
485
+ 'query_time_ms': self.query_time_diff_ms,
486
+ 'query_time_pct': self.query_time_diff_pct,
487
+ },
488
+ 'is_regression': self.is_regression(),
489
+ }
490
+
491
+
492
+ class PerfSnapshotManager:
493
+ """
494
+ Manager for performance snapshots.
495
+
496
+ Stores snapshots locally for baseline comparison.
497
+ OPT-IN: User must explicitly invoke snapshot commands.
498
+ """
499
+
500
+ DEFAULT_DIR = Path.home() / '.praisonai' / 'perf_snapshots'
501
+
502
+ def __init__(self, snapshot_dir: Optional[Path] = None):
503
+ self.snapshot_dir = snapshot_dir or self.DEFAULT_DIR
504
+ self.snapshot_dir.mkdir(parents=True, exist_ok=True)
505
+
506
+ def save(self, snapshot: PerfSnapshot) -> Path:
507
+ """Save a snapshot to disk."""
508
+ filename = f"{snapshot.name}_{snapshot.timestamp.replace(':', '-').replace('.', '-')}.json"
509
+ path = self.snapshot_dir / filename
510
+ with open(path, 'w') as f:
511
+ json.dump(snapshot.to_dict(), f, indent=2)
512
+ return path
513
+
514
+ def load(self, name: str) -> Optional[PerfSnapshot]:
515
+ """Load the most recent snapshot with given name."""
516
+ pattern = f"{name}_*.json"
517
+ files = sorted(self.snapshot_dir.glob(pattern), reverse=True)
518
+ if not files:
519
+ return None
520
+
521
+ with open(files[0]) as f:
522
+ data = json.load(f)
523
+ return PerfSnapshot.from_dict(data)
524
+
525
+ def load_baseline(self, name: str = 'baseline') -> Optional[PerfSnapshot]:
526
+ """Load the baseline snapshot."""
527
+ return self.load(name)
528
+
529
+ def save_baseline(self, snapshot: PerfSnapshot) -> Path:
530
+ """Save as baseline (overwrites previous baseline)."""
531
+ snapshot.name = 'baseline'
532
+ # Remove old baselines
533
+ for f in self.snapshot_dir.glob('baseline_*.json'):
534
+ f.unlink()
535
+ return self.save(snapshot)
536
+
537
+ def compare(self, current: PerfSnapshot, baseline_name: str = 'baseline') -> Optional[PerfComparison]:
538
+ """Compare current snapshot against baseline."""
539
+ baseline = self.load(baseline_name)
540
+ if not baseline:
541
+ return None
542
+ return PerfComparison(baseline=baseline, current=current)
543
+
544
+ def list_snapshots(self) -> List[str]:
545
+ """List all saved snapshot names."""
546
+ files = self.snapshot_dir.glob('*.json')
547
+ names = set()
548
+ for f in files:
549
+ # Extract name from filename (before first underscore)
550
+ name = f.stem.rsplit('_', 1)[0] if '_' in f.stem else f.stem
551
+ names.add(name)
552
+ return sorted(names)
553
+
554
+
555
+ # =============================================================================
556
+ # Utility Functions
557
+ # =============================================================================
558
+
559
+ def create_snapshot_from_suite(suite_result: Any, name: str = 'current') -> PerfSnapshot:
560
+ """
561
+ Create a PerfSnapshot from a SuiteResult.
562
+
563
+ Args:
564
+ suite_result: SuiteResult from profile suite
565
+ name: Name for the snapshot
566
+
567
+ Returns:
568
+ PerfSnapshot instance
569
+ """
570
+ # Get average query time from scenarios
571
+ query_times = []
572
+ first_token_times = []
573
+ for scenario in suite_result.scenarios:
574
+ query_times.extend(scenario.total_times)
575
+ first_token_times.extend(scenario.first_token_times)
576
+
577
+ avg_query_time = sum(query_times) / len(query_times) if query_times else 0.0
578
+ avg_first_token = sum(first_token_times) / len(first_token_times) if first_token_times else 0.0
579
+
580
+ # Get import time from analysis
581
+ import_time = 0.0
582
+ if suite_result.import_analysis:
583
+ import_time = suite_result.import_analysis[0].get('cumulative_ms', 0.0)
584
+
585
+ return PerfSnapshot(
586
+ timestamp=suite_result.timestamp,
587
+ name=name,
588
+ startup_cold_ms=suite_result.startup_cold_ms,
589
+ startup_warm_ms=suite_result.startup_warm_ms,
590
+ import_time_ms=import_time,
591
+ query_time_ms=avg_query_time,
592
+ first_token_ms=avg_first_token,
593
+ metadata=suite_result.metadata,
594
+ )
595
+
596
+
597
+ def format_comparison_report(comparison: PerfComparison) -> str:
598
+ """Format a comparison report as text."""
599
+ lines = []
600
+ lines.append("=" * 70)
601
+ lines.append("Performance Comparison Report")
602
+ lines.append("=" * 70)
603
+ lines.append("")
604
+
605
+ lines.append(f"Baseline: {comparison.baseline.name} ({comparison.baseline.timestamp})")
606
+ lines.append(f"Current: {comparison.current.name} ({comparison.current.timestamp})")
607
+ lines.append("")
608
+
609
+ lines.append("-" * 70)
610
+ lines.append(f"{'Metric':<25} {'Baseline':>12} {'Current':>12} {'Diff':>12} {'%':>8}")
611
+ lines.append("-" * 70)
612
+
613
+ def format_row(name: str, baseline: float, current: float, diff: float, pct: float) -> str:
614
+ sign = '+' if diff > 0 else ''
615
+ pct_sign = '+' if pct > 0 else ''
616
+ return f"{name:<25} {baseline:>12.2f} {current:>12.2f} {sign}{diff:>11.2f} {pct_sign}{pct:>7.1f}%"
617
+
618
+ lines.append(format_row(
619
+ "Startup Cold (ms)",
620
+ comparison.baseline.startup_cold_ms,
621
+ comparison.current.startup_cold_ms,
622
+ comparison.startup_cold_diff_ms,
623
+ comparison.startup_cold_diff_pct,
624
+ ))
625
+
626
+ lines.append(format_row(
627
+ "Import Time (ms)",
628
+ comparison.baseline.import_time_ms,
629
+ comparison.current.import_time_ms,
630
+ comparison.import_time_diff_ms,
631
+ comparison.import_time_diff_pct,
632
+ ))
633
+
634
+ lines.append(format_row(
635
+ "Query Time (ms)",
636
+ comparison.baseline.query_time_ms,
637
+ comparison.current.query_time_ms,
638
+ comparison.query_time_diff_ms,
639
+ comparison.query_time_diff_pct,
640
+ ))
641
+
642
+ lines.append("-" * 70)
643
+ lines.append("")
644
+
645
+ if comparison.is_regression():
646
+ lines.append("⚠️ REGRESSION DETECTED (>10% slower)")
647
+ else:
648
+ lines.append("✅ No significant regression")
649
+
650
+ lines.append("=" * 70)
651
+
652
+ return "\n".join(lines)