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,245 @@
1
+ """
2
+ In-memory / JSON file implementation of StateStore.
3
+
4
+ Zero external dependencies - uses Python built-ins.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import os
10
+ import threading
11
+ import time
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from .base import StateStore
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class MemoryStateStore(StateStore):
20
+ """
21
+ In-memory state store with optional JSON file persistence.
22
+
23
+ Zero external dependencies - ideal for development and testing.
24
+
25
+ Example:
26
+ # In-memory only
27
+ store = MemoryStateStore()
28
+
29
+ # With file persistence
30
+ store = MemoryStateStore(path="./state.json")
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ path: Optional[str] = None,
36
+ auto_save: bool = True,
37
+ save_interval: int = 60,
38
+ ):
39
+ """
40
+ Initialize memory state store.
41
+
42
+ Args:
43
+ path: Path to JSON file for persistence (None = in-memory only)
44
+ auto_save: Auto-save to file periodically
45
+ save_interval: Save interval in seconds
46
+ """
47
+ self.path = path
48
+ self.auto_save = auto_save
49
+ self.save_interval = save_interval
50
+
51
+ self._data: Dict[str, Any] = {}
52
+ self._ttls: Dict[str, float] = {} # key -> expiry timestamp
53
+ self._lock = threading.RLock()
54
+ self._last_save = time.time()
55
+
56
+ # Load from file if exists
57
+ if path and os.path.exists(path):
58
+ self._load()
59
+
60
+ logger.info(f"MemoryStateStore initialized (path={path})")
61
+
62
+ def _load(self) -> None:
63
+ """Load state from JSON file."""
64
+ if not self.path:
65
+ return
66
+
67
+ try:
68
+ with open(self.path, "r") as f:
69
+ saved = json.load(f)
70
+ self._data = saved.get("data", {})
71
+ self._ttls = saved.get("ttls", {})
72
+
73
+ # Clean expired keys
74
+ now = time.time()
75
+ expired = [k for k, exp in self._ttls.items() if exp <= now]
76
+ for k in expired:
77
+ self._data.pop(k, None)
78
+ self._ttls.pop(k, None)
79
+
80
+ logger.debug(f"Loaded {len(self._data)} keys from {self.path}")
81
+ except Exception as e:
82
+ logger.warning(f"Failed to load state from {self.path}: {e}")
83
+
84
+ def _save(self) -> None:
85
+ """Save state to JSON file."""
86
+ if not self.path:
87
+ return
88
+
89
+ try:
90
+ os.makedirs(os.path.dirname(self.path) or ".", exist_ok=True)
91
+ with open(self.path, "w") as f:
92
+ json.dump({"data": self._data, "ttls": self._ttls}, f)
93
+ self._last_save = time.time()
94
+ except Exception as e:
95
+ logger.warning(f"Failed to save state to {self.path}: {e}")
96
+
97
+ def _maybe_save(self) -> None:
98
+ """Save if auto_save is enabled and interval has passed."""
99
+ if self.auto_save and self.path:
100
+ if time.time() - self._last_save >= self.save_interval:
101
+ self._save()
102
+
103
+ def _check_ttl(self, key: str) -> bool:
104
+ """Check if key is expired. Returns True if valid, False if expired."""
105
+ if key not in self._ttls:
106
+ return True
107
+ if self._ttls[key] <= time.time():
108
+ self._data.pop(key, None)
109
+ self._ttls.pop(key, None)
110
+ return False
111
+ return True
112
+
113
+ def get(self, key: str) -> Optional[Any]:
114
+ """Get a value by key."""
115
+ with self._lock:
116
+ if not self._check_ttl(key):
117
+ return None
118
+ return self._data.get(key)
119
+
120
+ def set(
121
+ self,
122
+ key: str,
123
+ value: Any,
124
+ ttl: Optional[int] = None
125
+ ) -> None:
126
+ """Set a value with optional TTL."""
127
+ with self._lock:
128
+ self._data[key] = value
129
+ if ttl:
130
+ self._ttls[key] = time.time() + ttl
131
+ elif key in self._ttls:
132
+ del self._ttls[key]
133
+ self._maybe_save()
134
+
135
+ def delete(self, key: str) -> bool:
136
+ """Delete a key."""
137
+ with self._lock:
138
+ existed = key in self._data
139
+ self._data.pop(key, None)
140
+ self._ttls.pop(key, None)
141
+ self._maybe_save()
142
+ return existed
143
+
144
+ def exists(self, key: str) -> bool:
145
+ """Check if a key exists."""
146
+ with self._lock:
147
+ if not self._check_ttl(key):
148
+ return False
149
+ return key in self._data
150
+
151
+ def keys(self, pattern: str = "*") -> List[str]:
152
+ """List keys matching pattern."""
153
+ import fnmatch
154
+
155
+ with self._lock:
156
+ # Clean expired keys first
157
+ now = time.time()
158
+ expired = [k for k, exp in self._ttls.items() if exp <= now]
159
+ for k in expired:
160
+ self._data.pop(k, None)
161
+ self._ttls.pop(k, None)
162
+
163
+ if pattern == "*":
164
+ return list(self._data.keys())
165
+ return [k for k in self._data.keys() if fnmatch.fnmatch(k, pattern)]
166
+
167
+ def ttl(self, key: str) -> Optional[int]:
168
+ """Get remaining TTL in seconds."""
169
+ with self._lock:
170
+ if key not in self._ttls:
171
+ return None
172
+ remaining = self._ttls[key] - time.time()
173
+ if remaining <= 0:
174
+ self._data.pop(key, None)
175
+ self._ttls.pop(key, None)
176
+ return None
177
+ return int(remaining)
178
+
179
+ def expire(self, key: str, ttl: int) -> bool:
180
+ """Set TTL on existing key."""
181
+ with self._lock:
182
+ if key not in self._data:
183
+ return False
184
+ self._ttls[key] = time.time() + ttl
185
+ self._maybe_save()
186
+ return True
187
+
188
+ def hget(self, key: str, field: str) -> Optional[Any]:
189
+ """Get a field from a hash."""
190
+ with self._lock:
191
+ if not self._check_ttl(key):
192
+ return None
193
+ data = self._data.get(key)
194
+ if not isinstance(data, dict):
195
+ return None
196
+ return data.get(field)
197
+
198
+ def hset(self, key: str, field: str, value: Any) -> None:
199
+ """Set a field in a hash."""
200
+ with self._lock:
201
+ if key not in self._data or not isinstance(self._data[key], dict):
202
+ self._data[key] = {}
203
+ self._data[key][field] = value
204
+ self._maybe_save()
205
+
206
+ def hgetall(self, key: str) -> Dict[str, Any]:
207
+ """Get all fields from a hash."""
208
+ with self._lock:
209
+ if not self._check_ttl(key):
210
+ return {}
211
+ data = self._data.get(key)
212
+ if not isinstance(data, dict):
213
+ return {}
214
+ return dict(data)
215
+
216
+ def hdel(self, key: str, *fields: str) -> int:
217
+ """Delete fields from a hash."""
218
+ with self._lock:
219
+ if key not in self._data or not isinstance(self._data[key], dict):
220
+ return 0
221
+ count = 0
222
+ for field in fields:
223
+ if field in self._data[key]:
224
+ del self._data[key][field]
225
+ count += 1
226
+ self._maybe_save()
227
+ return count
228
+
229
+ def flush(self) -> None:
230
+ """Force save to file."""
231
+ with self._lock:
232
+ self._save()
233
+
234
+ def clear(self) -> None:
235
+ """Clear all data."""
236
+ with self._lock:
237
+ self._data.clear()
238
+ self._ttls.clear()
239
+ self._maybe_save()
240
+
241
+ def close(self) -> None:
242
+ """Close the store and save."""
243
+ with self._lock:
244
+ if self.path:
245
+ self._save()
@@ -0,0 +1,158 @@
1
+ """
2
+ MongoDB implementation of StateStore.
3
+
4
+ Requires: pymongo
5
+ Install: pip install pymongo
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import time
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from .base import StateStore
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class MongoDBStateStore(StateStore):
19
+ """
20
+ MongoDB-based state store.
21
+
22
+ Example:
23
+ store = MongoDBStateStore(
24
+ url="mongodb://localhost:27017",
25
+ database="praisonai"
26
+ )
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ url: str = "mongodb://localhost:27017",
32
+ database: str = "praisonai",
33
+ collection: str = "state",
34
+ ):
35
+ try:
36
+ from pymongo import MongoClient
37
+ except ImportError:
38
+ raise ImportError(
39
+ "pymongo is required for MongoDB support. "
40
+ "Install with: pip install pymongo"
41
+ )
42
+
43
+ self._client = MongoClient(url)
44
+ self._db = self._client[database]
45
+ self._collection = self._db[collection]
46
+
47
+ # Create TTL index for automatic expiration
48
+ self._collection.create_index("expires_at", expireAfterSeconds=0)
49
+
50
+ logger.info(f"Connected to MongoDB: {database}.{collection}")
51
+
52
+ def get(self, key: str) -> Optional[Any]:
53
+ """Get a value by key."""
54
+ doc = self._collection.find_one({"_id": key})
55
+ if not doc:
56
+ return None
57
+
58
+ # Check TTL
59
+ if doc.get("expires_at") and doc["expires_at"] <= time.time():
60
+ self._collection.delete_one({"_id": key})
61
+ return None
62
+
63
+ return doc.get("value")
64
+
65
+ def set(
66
+ self,
67
+ key: str,
68
+ value: Any,
69
+ ttl: Optional[int] = None
70
+ ) -> None:
71
+ """Set a value with optional TTL."""
72
+ doc = {"_id": key, "value": value, "updated_at": time.time()}
73
+
74
+ if ttl:
75
+ from datetime import datetime, timedelta
76
+ doc["expires_at"] = datetime.utcnow() + timedelta(seconds=ttl)
77
+
78
+ self._collection.replace_one({"_id": key}, doc, upsert=True)
79
+
80
+ def delete(self, key: str) -> bool:
81
+ """Delete a key."""
82
+ result = self._collection.delete_one({"_id": key})
83
+ return result.deleted_count > 0
84
+
85
+ def exists(self, key: str) -> bool:
86
+ """Check if a key exists."""
87
+ doc = self._collection.find_one({"_id": key}, {"_id": 1, "expires_at": 1})
88
+ if not doc:
89
+ return False
90
+ if doc.get("expires_at") and doc["expires_at"] <= time.time():
91
+ return False
92
+ return True
93
+
94
+ def keys(self, pattern: str = "*") -> List[str]:
95
+ """List keys matching pattern."""
96
+ if pattern == "*":
97
+ cursor = self._collection.find({}, {"_id": 1})
98
+ else:
99
+ # Convert glob pattern to regex
100
+ import re
101
+ regex = pattern.replace("*", ".*").replace("?", ".")
102
+ cursor = self._collection.find({"_id": {"$regex": f"^{regex}$"}}, {"_id": 1})
103
+
104
+ return [doc["_id"] for doc in cursor]
105
+
106
+ def ttl(self, key: str) -> Optional[int]:
107
+ """Get remaining TTL in seconds."""
108
+ doc = self._collection.find_one({"_id": key}, {"expires_at": 1})
109
+ if not doc or "expires_at" not in doc:
110
+ return None
111
+
112
+ remaining = doc["expires_at"].timestamp() - time.time()
113
+ if remaining <= 0:
114
+ return None
115
+ return int(remaining)
116
+
117
+ def expire(self, key: str, ttl: int) -> bool:
118
+ """Set TTL on existing key."""
119
+ from datetime import datetime, timedelta
120
+
121
+ result = self._collection.update_one(
122
+ {"_id": key},
123
+ {"$set": {"expires_at": datetime.utcnow() + timedelta(seconds=ttl)}}
124
+ )
125
+ return result.modified_count > 0
126
+
127
+ def hget(self, key: str, field: str) -> Optional[Any]:
128
+ """Get a field from a hash."""
129
+ doc = self._collection.find_one({"_id": key})
130
+ if not doc or not isinstance(doc.get("value"), dict):
131
+ return None
132
+ return doc["value"].get(field)
133
+
134
+ def hset(self, key: str, field: str, value: Any) -> None:
135
+ """Set a field in a hash."""
136
+ self._collection.update_one(
137
+ {"_id": key},
138
+ {"$set": {f"value.{field}": value, "updated_at": time.time()}},
139
+ upsert=True
140
+ )
141
+
142
+ def hgetall(self, key: str) -> Dict[str, Any]:
143
+ """Get all fields from a hash."""
144
+ doc = self._collection.find_one({"_id": key})
145
+ if not doc or not isinstance(doc.get("value"), dict):
146
+ return {}
147
+ return doc["value"]
148
+
149
+ def hdel(self, key: str, *fields: str) -> int:
150
+ """Delete fields from a hash."""
151
+ unset = {f"value.{field}": "" for field in fields}
152
+ result = self._collection.update_one({"_id": key}, {"$unset": unset})
153
+ return len(fields) if result.modified_count > 0 else 0
154
+
155
+ def close(self) -> None:
156
+ """Close the store."""
157
+ if self._client:
158
+ self._client.close()
@@ -0,0 +1,190 @@
1
+ """
2
+ Redis implementation of StateStore.
3
+
4
+ Requires: redis
5
+ Install: pip install redis
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from .base import StateStore
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class RedisStateStore(StateStore):
18
+ """
19
+ Redis-based state store for fast key-value operations.
20
+
21
+ Example:
22
+ store = RedisStateStore(
23
+ url="redis://localhost:6379"
24
+ )
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ url: Optional[str] = None,
30
+ host: str = "localhost",
31
+ port: int = 6379,
32
+ db: int = 0,
33
+ password: Optional[str] = None,
34
+ prefix: str = "praison:",
35
+ decode_responses: bool = True,
36
+ socket_timeout: int = 5,
37
+ max_connections: int = 10,
38
+ ):
39
+ """
40
+ Initialize Redis state store.
41
+
42
+ Args:
43
+ url: Full Redis URL (overrides host/port/db/password)
44
+ host: Redis host
45
+ port: Redis port
46
+ db: Redis database number
47
+ password: Redis password
48
+ prefix: Key prefix for namespacing
49
+ decode_responses: Decode bytes to strings
50
+ socket_timeout: Socket timeout in seconds
51
+ max_connections: Max connections in pool
52
+ """
53
+ try:
54
+ import redis as redis_lib
55
+ except ImportError:
56
+ raise ImportError(
57
+ "redis is required for Redis support. "
58
+ "Install with: pip install redis"
59
+ )
60
+
61
+ self._redis_lib = redis_lib
62
+ self.prefix = prefix
63
+
64
+ if url:
65
+ self._client = redis_lib.from_url(
66
+ url,
67
+ decode_responses=decode_responses,
68
+ socket_timeout=socket_timeout,
69
+ max_connections=max_connections,
70
+ )
71
+ else:
72
+ pool = redis_lib.ConnectionPool(
73
+ host=host,
74
+ port=port,
75
+ db=db,
76
+ password=password,
77
+ decode_responses=decode_responses,
78
+ socket_timeout=socket_timeout,
79
+ max_connections=max_connections,
80
+ )
81
+ self._client = redis_lib.Redis(connection_pool=pool)
82
+
83
+ # Test connection
84
+ self._client.ping()
85
+ logger.info(f"Connected to Redis at {url or f'{host}:{port}'}")
86
+
87
+ def _key(self, key: str) -> str:
88
+ """Add prefix to key."""
89
+ return f"{self.prefix}{key}"
90
+
91
+ def get(self, key: str) -> Optional[Any]:
92
+ """Get a value by key."""
93
+ value = self._client.get(self._key(key))
94
+ if value is None:
95
+ return None
96
+ # Try to deserialize JSON
97
+ try:
98
+ return json.loads(value)
99
+ except (json.JSONDecodeError, TypeError):
100
+ return value
101
+
102
+ def set(
103
+ self,
104
+ key: str,
105
+ value: Any,
106
+ ttl: Optional[int] = None
107
+ ) -> None:
108
+ """Set a value with optional TTL."""
109
+ # Serialize non-string values
110
+ if not isinstance(value, str):
111
+ value = json.dumps(value)
112
+
113
+ if ttl:
114
+ self._client.setex(self._key(key), ttl, value)
115
+ else:
116
+ self._client.set(self._key(key), value)
117
+
118
+ def delete(self, key: str) -> bool:
119
+ """Delete a key."""
120
+ return self._client.delete(self._key(key)) > 0
121
+
122
+ def exists(self, key: str) -> bool:
123
+ """Check if a key exists."""
124
+ return self._client.exists(self._key(key)) > 0
125
+
126
+ def keys(self, pattern: str = "*") -> List[str]:
127
+ """List keys matching pattern."""
128
+ full_pattern = self._key(pattern)
129
+ keys = self._client.keys(full_pattern)
130
+ # Remove prefix from returned keys
131
+ prefix_len = len(self.prefix)
132
+ return [k[prefix_len:] if k.startswith(self.prefix) else k for k in keys]
133
+
134
+ def ttl(self, key: str) -> Optional[int]:
135
+ """Get remaining TTL in seconds."""
136
+ result = self._client.ttl(self._key(key))
137
+ if result < 0: # -1 = no TTL, -2 = key doesn't exist
138
+ return None
139
+ return result
140
+
141
+ def expire(self, key: str, ttl: int) -> bool:
142
+ """Set TTL on existing key."""
143
+ return self._client.expire(self._key(key), ttl)
144
+
145
+ def hget(self, key: str, field: str) -> Optional[Any]:
146
+ """Get a field from a hash."""
147
+ value = self._client.hget(self._key(key), field)
148
+ if value is None:
149
+ return None
150
+ try:
151
+ return json.loads(value)
152
+ except (json.JSONDecodeError, TypeError):
153
+ return value
154
+
155
+ def hset(self, key: str, field: str, value: Any) -> None:
156
+ """Set a field in a hash."""
157
+ if not isinstance(value, str):
158
+ value = json.dumps(value)
159
+ self._client.hset(self._key(key), field, value)
160
+
161
+ def hgetall(self, key: str) -> Dict[str, Any]:
162
+ """Get all fields from a hash."""
163
+ data = self._client.hgetall(self._key(key))
164
+ result = {}
165
+ for k, v in data.items():
166
+ try:
167
+ result[k] = json.loads(v)
168
+ except (json.JSONDecodeError, TypeError):
169
+ result[k] = v
170
+ return result
171
+
172
+ def hdel(self, key: str, *fields: str) -> int:
173
+ """Delete fields from a hash."""
174
+ if not fields:
175
+ return 0
176
+ return self._client.hdel(self._key(key), *fields)
177
+
178
+ def incr(self, key: str, amount: int = 1) -> int:
179
+ """Increment a counter."""
180
+ return self._client.incrby(self._key(key), amount)
181
+
182
+ def decr(self, key: str, amount: int = 1) -> int:
183
+ """Decrement a counter."""
184
+ return self._client.decrby(self._key(key), amount)
185
+
186
+ def close(self) -> None:
187
+ """Close the store."""
188
+ if self._client:
189
+ self._client.close()
190
+ self._client = None