clsplusplus 7.2.4__tar.gz → 7.3.1__tar.gz

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 (180) hide show
  1. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/PKG-INFO +10 -1
  2. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/pyproject.toml +35 -1
  3. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/api.py +258 -1
  4. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/config.py +13 -0
  5. clsplusplus-7.3.1/src/clsplusplus/embeddings.py +173 -0
  6. clsplusplus-7.3.1/src/clsplusplus/integrations/__init__.py +46 -0
  7. clsplusplus-7.3.1/src/clsplusplus/integrations/autogen.py +283 -0
  8. clsplusplus-7.3.1/src/clsplusplus/integrations/crewai.py +183 -0
  9. clsplusplus-7.3.1/src/clsplusplus/integrations/langgraph.py +330 -0
  10. clsplusplus-7.3.1/src/clsplusplus/integrations/llamaindex.py +253 -0
  11. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/local_routes.py +6 -9
  12. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/memory_phase.py +94 -15
  13. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/memory_service.py +92 -0
  14. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/middleware.py +2 -0
  15. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/models.py +3 -0
  16. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/oauth_server.py +62 -1
  17. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/oauth_store.py +9 -9
  18. clsplusplus-7.3.1/src/clsplusplus/write_metrics.py +74 -0
  19. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/PKG-INFO +10 -1
  20. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/SOURCES.txt +14 -1
  21. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/requires.txt +13 -0
  22. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_admin.py +54 -0
  23. clsplusplus-7.3.1/tests/test_autogen_memory.py +350 -0
  24. clsplusplus-7.3.1/tests/test_crewai_provider.py +161 -0
  25. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_embeddings.py +112 -8
  26. clsplusplus-7.3.1/tests/test_extension_cross_model_runtime.py +280 -0
  27. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_extension_integration.py +26 -2
  28. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_extension_ui.py +1 -1
  29. clsplusplus-7.3.1/tests/test_integration_sync_cli_contract.py +129 -0
  30. clsplusplus-7.3.1/tests/test_langgraph_store.py +341 -0
  31. clsplusplus-7.3.1/tests/test_llamaindex_block.py +288 -0
  32. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_mcp_oauth.py +53 -0
  33. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_memory_phase.py +75 -25
  34. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_reconsolidation.py +130 -1
  35. clsplusplus-7.3.1/tests/test_write_metrics.py +100 -0
  36. clsplusplus-7.2.4/src/clsplusplus/embeddings.py +0 -52
  37. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/LICENSE +0 -0
  38. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/README.md +0 -0
  39. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/setup.cfg +0 -0
  40. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/__init__.py +0 -0
  41. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/abuse_guard.py +0 -0
  42. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/api_usage_analytics.py +0 -0
  43. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/auth.py +0 -0
  44. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/cli.py +0 -0
  45. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/client.py +0 -0
  46. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/cost_forecast.py +0 -0
  47. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/cost_model.py +0 -0
  48. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/debug_console.py +0 -0
  49. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/demo_llm.py +0 -0
  50. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/demo_llm_calls.py +0 -0
  51. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/demo_local.py +0 -0
  52. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/email_service.py +0 -0
  53. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/funnel_routes.py +0 -0
  54. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/geo.py +0 -0
  55. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/health_metrics.py +0 -0
  56. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/homepage_autopromote.py +0 -0
  57. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/idempotency.py +0 -0
  58. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/integration_service.py +0 -0
  59. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/jwt_utils.py +0 -0
  60. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/main.py +0 -0
  61. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/mcp_http.py +0 -0
  62. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/mcp_server.py +0 -0
  63. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/memory_cycle.py +0 -0
  64. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/__init__.py +0 -0
  65. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/__main__.py +0 -0
  66. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/billing.py +0 -0
  67. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/healthcheck.py +0 -0
  68. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/notifier.py +0 -0
  69. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/pricing.py +0 -0
  70. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/reconciler.py +0 -0
  71. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/schema.py +0 -0
  72. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metering_v2/writer.py +0 -0
  73. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/metrics.py +0 -0
  74. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/namespace_resolver.py +0 -0
  75. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/permissions.py +0 -0
  76. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/plasticity.py +0 -0
  77. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/pricing.py +0 -0
  78. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/pricing_models.py +0 -0
  79. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/pricing_store.py +0 -0
  80. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/prompt_log.py +0 -0
  81. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/rate_limit.py +0 -0
  82. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/razorpay_service.py +0 -0
  83. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/rbac_service.py +0 -0
  84. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/reconsolidation.py +0 -0
  85. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/resilience.py +0 -0
  86. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/sleep_cycle.py +0 -0
  87. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/__init__.py +0 -0
  88. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/base.py +0 -0
  89. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/chat_session_store.py +0 -0
  90. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/integration_store.py +0 -0
  91. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/l0_working_buffer.py +0 -0
  92. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/l1_indexing_store.py +0 -0
  93. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/l2_schema_graph.py +0 -0
  94. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/l3_deep_recess.py +0 -0
  95. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/l3_postgres.py +0 -0
  96. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/rbac_store.py +0 -0
  97. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/user_store.py +0 -0
  98. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/waitlist_store.py +0 -0
  99. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stores/web_events_store.py +0 -0
  100. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/stripe_service.py +0 -0
  101. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/subscription_watchdog.py +0 -0
  102. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/temporal.py +0 -0
  103. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/test_suite.py +0 -0
  104. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/tier_resolver.py +0 -0
  105. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/tiers.py +0 -0
  106. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/topical_resonance.py +0 -0
  107. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/tracer.py +0 -0
  108. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/usage.py +0 -0
  109. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/user_embeddings.py +0 -0
  110. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/user_pulse.py +0 -0
  111. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/user_service.py +0 -0
  112. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/waitlist_service.py +0 -0
  113. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/webhook_dispatcher.py +0 -0
  114. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/weblab.py +0 -0
  115. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/weblab_watcher.py +0 -0
  116. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus/window_limits.py +0 -0
  117. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/dependency_links.txt +0 -0
  118. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/entry_points.txt +0 -0
  119. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/src/clsplusplus.egg-info/top_level.txt +0 -0
  120. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_abuse_guard.py +0 -0
  121. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_admin_seed.py +0 -0
  122. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_api.py +0 -0
  123. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_api_comprehensive.py +0 -0
  124. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_api_endpoints.py +0 -0
  125. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_api_usage_analytics.py +0 -0
  126. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_auth.py +0 -0
  127. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_auth_me_api_key.py +0 -0
  128. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_billing_e2e.py +0 -0
  129. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_burst_hardening.py +0 -0
  130. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_client_sdk.py +0 -0
  131. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_config.py +0 -0
  132. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_cost_forecast.py +0 -0
  133. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_cross_llm_memory.py +0 -0
  134. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_debug_console.py +0 -0
  135. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_demo_llm.py +0 -0
  136. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_feedback_github.py +0 -0
  137. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_free_tier_lifecycle.py +0 -0
  138. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_full_api_coverage.py +0 -0
  139. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_funnel_metrics.py +0 -0
  140. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_geo_gating.py +0 -0
  141. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_health_metrics.py +0 -0
  142. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_idempotency.py +0 -0
  143. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_integration_service.py +0 -0
  144. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_mcp_connect.py +0 -0
  145. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_memory_cycle.py +0 -0
  146. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_memory_service.py +0 -0
  147. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_memory_write_no_schema_garbage.py +0 -0
  148. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_healthcheck.py +0 -0
  149. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_notifier.py +0 -0
  150. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_pricing.py +0 -0
  151. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_reconciler.py +0 -0
  152. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_schema.py +0 -0
  153. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_metering_v2_writer.py +0 -0
  154. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_middleware.py +0 -0
  155. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_models.py +0 -0
  156. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_overage_billing.py +0 -0
  157. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_performance.py +0 -0
  158. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_plasticity.py +0 -0
  159. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_pricing.py +0 -0
  160. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_prototype_e2e.py +0 -0
  161. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_rate_limit.py +0 -0
  162. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_razorpay_billing.py +0 -0
  163. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_razorpay_subscription_webhooks.py +0 -0
  164. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_regression.py +0 -0
  165. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_resilience.py +0 -0
  166. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_security.py +0 -0
  167. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_sleep_cycle.py +0 -0
  168. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_stores.py +0 -0
  169. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_subscription_watchdog.py +0 -0
  170. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_tier_resolver.py +0 -0
  171. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_tiers.py +0 -0
  172. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_usage.py +0 -0
  173. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_user_auth.py +0 -0
  174. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_user_embeddings.py +0 -0
  175. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_user_pulse.py +0 -0
  176. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_user_stories.py +0 -0
  177. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_waitlist.py +0 -0
  178. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_webhook_dispatcher.py +0 -0
  179. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_weblab.py +0 -0
  180. {clsplusplus-7.2.4 → clsplusplus-7.3.1}/tests/test_window_limits.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clsplusplus
3
- Version: 7.2.4
3
+ Version: 7.3.1
4
4
  Summary: Brain-inspired, model-agnostic persistent memory for LLMs. Learn, recall, forget — like a brain. Works with OpenAI, Claude, Gemini, Llama.
5
5
  Author-email: AlphaForge AI Labs <contact@alphaforge.ai>
6
6
  Maintainer-email: Rajamohan Jabbala <contact@alphaforge.ai>
@@ -52,6 +52,15 @@ Requires-Dist: razorpay>=1.4.0; extra == "server"
52
52
  Requires-Dist: minio>=7.2.0; extra == "server"
53
53
  Requires-Dist: pyarrow>=14.0.0; extra == "server"
54
54
  Requires-Dist: posthog>=3.5.0; extra == "server"
55
+ Provides-Extra: crewai
56
+ Requires-Dist: crewai>=1.0.0; extra == "crewai"
57
+ Provides-Extra: langgraph
58
+ Requires-Dist: langgraph>=1.0.0; extra == "langgraph"
59
+ Provides-Extra: llamaindex
60
+ Requires-Dist: llama-index-core>=0.12.0; extra == "llamaindex"
61
+ Provides-Extra: autogen
62
+ Requires-Dist: autogen-core>=0.4.0; extra == "autogen"
63
+ Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen"
55
64
  Provides-Extra: dev
56
65
  Requires-Dist: pytest>=7.4.0; extra == "dev"
57
66
  Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clsplusplus"
7
- version = "7.2.4"
7
+ version = "7.3.1"
8
8
  description = "Brain-inspired, model-agnostic persistent memory for LLMs. Learn, recall, forget — like a brain. Works with OpenAI, Claude, Gemini, Llama."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -63,6 +63,40 @@ server = [
63
63
  "pyarrow>=14.0.0",
64
64
  "posthog>=3.5.0",
65
65
  ]
66
+ # CrewAI integration — pip install clsplusplus[crewai]
67
+ # Provides clsplusplus.integrations.crewai.CLSMemoryStorage, a native CrewAI
68
+ # external-memory backend. Floor is the CrewAI 1.x line that ships the stable
69
+ # crewai.memory.storage.interface.Storage + ExternalMemory API this adapts to.
70
+ crewai = [
71
+ "crewai>=1.0.0",
72
+ ]
73
+ # LangGraph integration — pip install clsplusplus[langgraph]
74
+ # Provides clsplusplus.integrations.langgraph.CLSMemoryStore, a native
75
+ # subclass of langgraph.store.base.BaseStore (the long-term-memory store you
76
+ # pass as store= to graph.compile). Floor is the LangGraph 1.x line; BaseStore
77
+ # and its Op/Item/SearchItem types are re-exported from langgraph-checkpoint
78
+ # and stable across the 1.x series.
79
+ langgraph = [
80
+ "langgraph>=1.0.0",
81
+ ]
82
+ # LlamaIndex integration — pip install clsplusplus[llamaindex]
83
+ # Provides clsplusplus.integrations.llamaindex.CLSMemoryBlock, a native subclass
84
+ # of llama_index.core.memory.BaseMemoryBlock (a pluggable memory_block you pass
85
+ # to Memory.from_defaults). Floor is the 0.12 line that introduced the modern
86
+ # Memory + BaseMemoryBlock API; verified against llama-index-core 0.14.x.
87
+ llamaindex = [
88
+ "llama-index-core>=0.12.0",
89
+ ]
90
+ # AutoGen integration — pip install clsplusplus[autogen]
91
+ # Provides clsplusplus.integrations.autogen.CLSMemory, a native implementation
92
+ # of Microsoft AutoGen's autogen_core.memory.Memory protocol (the object you
93
+ # pass to AssistantAgent(memory=[...])). Floors target the new AutoGen line
94
+ # (autogen-core / autogen-agentchat 0.4+); the Memory protocol + MemoryContent /
95
+ # MemoryQueryResult / UpdateContextResult shapes are stable across 0.4 → 0.7.x.
96
+ autogen = [
97
+ "autogen-core>=0.4.0",
98
+ "autogen-agentchat>=0.4.0",
99
+ ]
66
100
  dev = [
67
101
  "pytest>=7.4.0",
68
102
  "pytest-asyncio>=0.23.0",
@@ -25,6 +25,7 @@ from clsplusplus.user_service import UserService
25
25
  from clsplusplus.middleware import AuthMiddleware, HealthMetricsMiddleware, QuotaMiddleware, RateLimitMiddleware, RequestIdMiddleware, TracingMiddleware
26
26
  from clsplusplus.abuse_guard import AbuseGuardMiddleware
27
27
  from clsplusplus.tracer import tracer
28
+ from clsplusplus import write_metrics
28
29
  from clsplusplus.models import (
29
30
  AdjudicateRequest,
30
31
  ApiKeyCreate,
@@ -480,6 +481,20 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
480
481
  "distinct_id": distinct_id,
481
482
  }
482
483
 
484
+ @app.get("/v1/config/extension")
485
+ async def extension_provider_config():
486
+ """Hot-updatable provider URL rules for the Chrome extension.
487
+
488
+ When ChatGPT/Claude/Gemini change API paths, update extension/provider_rules.json
489
+ on the server — clients pick it up without a Chrome Web Store redeploy.
490
+ """
491
+ rules_path = FilePath(__file__).resolve().parents[2] / "extension" / "provider_rules.json"
492
+ try:
493
+ import json as _json
494
+ return _json.loads(rules_path.read_text(encoding="utf-8"))
495
+ except Exception:
496
+ return {"version": 0, "providers": {}}
497
+
483
498
  @app.get("/admin/weblab")
484
499
  async def admin_weblab_status(request: Request):
485
500
  """Weblab control status — watched flags + latest green/red verdict."""
@@ -1232,7 +1247,14 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
1232
1247
  "store": "Setup",
1233
1248
  "error": "Add CLS_REDIS_URL and CLS_DATABASE_URL in Render Dashboard.",
1234
1249
  }
1235
- return HealthResponse(status=h["status"], stores=stores)
1250
+ wm = write_metrics.snapshot()
1251
+ return HealthResponse(
1252
+ status=h["status"],
1253
+ stores=stores,
1254
+ writes=wm["total_writes"],
1255
+ degraded_writes=wm["degraded_writes"],
1256
+ degraded_write_rate=wm["degraded_write_rate"],
1257
+ )
1236
1258
 
1237
1259
  @app.get("/v1/health/score", response_model=HealthResponse)
1238
1260
  async def health_score():
@@ -2430,6 +2452,241 @@ def create_app(settings: Optional[Settings] = None) -> FastAPI:
2430
2452
  logger.error("Admin user list error: %s: %s", type(e).__name__, e)
2431
2453
  raise HTTPException(status_code=500, detail="User data unavailable")
2432
2454
 
2455
+ @app.get("/admin/integrations")
2456
+ async def admin_integrations(request: Request):
2457
+ """Per-integration release sync: LIVE published version of each
2458
+ publishable artifact vs the current main release tag.
2459
+
2460
+ The "main release tag" is the single source of truth that every
2461
+ integration is supposed to track (the rule: the release tag IS the
2462
+ version for every artifact). We read it from the newest git tag; if
2463
+ the deployed image has stripped `.git`, we fall back to the committed
2464
+ package version. Each integration's `live_version` is read from its
2465
+ own registry/manifest using the same checks the sync tooling uses.
2466
+
2467
+ Fully resilient: every external/registry probe degrades to a
2468
+ `live_version` of None and `in_sync: false` rather than failing the
2469
+ request. This endpoint NEVER returns 500.
2470
+ """
2471
+ _require_admin(request)
2472
+
2473
+ # --- main release tag: the source of truth every artifact tracks ---
2474
+ def _read_main_tag() -> str:
2475
+ # 1) newest git tag (release-of-record).
2476
+ try:
2477
+ import subprocess
2478
+ repo_root = FilePath(__file__).resolve().parent.parent.parent
2479
+ out = subprocess.run(
2480
+ ["git", "describe", "--tags", "--abbrev=0"],
2481
+ cwd=str(repo_root),
2482
+ capture_output=True,
2483
+ timeout=5,
2484
+ )
2485
+ if out.returncode == 0:
2486
+ tag = out.stdout.decode().strip()
2487
+ if tag:
2488
+ return tag
2489
+ except Exception:
2490
+ pass
2491
+ # 2) committed package version (prod images often strip .git).
2492
+ try:
2493
+ pyproject = FilePath(__file__).resolve().parent.parent.parent / "pyproject.toml"
2494
+ if pyproject.exists():
2495
+ for line in pyproject.read_text().splitlines():
2496
+ s = line.strip()
2497
+ if s.startswith("version") and "=" in s:
2498
+ v = s.split("=", 1)[1].strip().strip('"').strip("'")
2499
+ if v:
2500
+ return f"v{v}"
2501
+ except Exception:
2502
+ pass
2503
+ # 3) last resort: in-package __version__.
2504
+ try:
2505
+ from clsplusplus import __version__ as _v
2506
+ if _v:
2507
+ return f"v{_v}"
2508
+ except Exception:
2509
+ pass
2510
+ return "unknown"
2511
+
2512
+ def _norm(v):
2513
+ """Normalise a version for comparison: drop leading 'v', None-safe."""
2514
+ if not v:
2515
+ return None
2516
+ return str(v).strip().lstrip("v") or None
2517
+
2518
+ def _http_json(url: str, timeout: float = 4.0):
2519
+ """Best-effort HTTP GET -> parsed JSON; None on any failure."""
2520
+ try:
2521
+ import json as _json
2522
+ import urllib.request
2523
+ req = urllib.request.Request(url, headers={"User-Agent": "cls-admin-integrations"})
2524
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
2525
+ return _json.loads(resp.read().decode())
2526
+ except Exception:
2527
+ return None
2528
+
2529
+ # --- per-integration LIVE version probes (each isolated/best-effort) ---
2530
+ def _npm_live() -> Optional[str]:
2531
+ # registry.npmjs.org dist-tag latest (matches `npm view ... version`)
2532
+ data = _http_json("https://registry.npmjs.org/clsplusplus")
2533
+ if isinstance(data, dict):
2534
+ latest = (data.get("dist-tags") or {}).get("latest")
2535
+ if latest:
2536
+ return latest
2537
+ return None
2538
+
2539
+ def _pypi_live() -> Optional[str]:
2540
+ data = _http_json("https://pypi.org/pypi/clsplusplus/json")
2541
+ if isinstance(data, dict):
2542
+ return (data.get("info") or {}).get("version")
2543
+ return None
2544
+
2545
+ def _mcp_registry_live() -> Optional[str]:
2546
+ data = _http_json(
2547
+ "https://registry.modelcontextprotocol.io/v0/servers"
2548
+ "?search=io.github.rajamohan1950/cls-memory"
2549
+ )
2550
+ if isinstance(data, dict):
2551
+ servers = data.get("servers") or data.get("data") or []
2552
+ for s in servers if isinstance(servers, list) else []:
2553
+ name = s.get("name") if isinstance(s, dict) else None
2554
+ if name and "cls-memory" in name:
2555
+ # version may live at top level or nested under meta.
2556
+ return (
2557
+ s.get("version")
2558
+ or (s.get("version_detail") or {}).get("version")
2559
+ or (s.get("_meta") or {}).get("version")
2560
+ )
2561
+ return None
2562
+
2563
+ def _extension_live() -> Optional[str]:
2564
+ # The Chrome Web Store artifact == the committed manifest version.
2565
+ try:
2566
+ import json as _json
2567
+ manifest = FilePath(__file__).resolve().parent.parent.parent / "extension" / "manifest.json"
2568
+ if manifest.exists():
2569
+ return _json.loads(manifest.read_text()).get("version")
2570
+ except Exception:
2571
+ pass
2572
+ return None
2573
+
2574
+ def _remote_mcp_live() -> Optional[str]:
2575
+ # Live hosted MCP endpoint: serverInfo.version from initialize, if
2576
+ # exposed; the metadata doc otherwise. Best-effort only.
2577
+ doc = _http_json(
2578
+ "https://www.clsplusplus.com/.well-known/oauth-protected-resource",
2579
+ timeout=3.0,
2580
+ )
2581
+ if isinstance(doc, dict):
2582
+ v = doc.get("version") or (doc.get("serverInfo") or {}).get("version")
2583
+ if v:
2584
+ return v
2585
+ return None
2586
+
2587
+ try:
2588
+ # The version probes below each block on network I/O (urllib) or a
2589
+ # `git` subprocess. Offload every blocking probe to a worker thread
2590
+ # and run them concurrently so this async handler never stalls the
2591
+ # event loop (previously up to ~15s of serial blocking). Each probe
2592
+ # is already isolated/best-effort, so any failure degrades to None.
2593
+ (
2594
+ main_tag,
2595
+ npm_live,
2596
+ pypi_live,
2597
+ mcp_registry_live,
2598
+ extension_live,
2599
+ remote_mcp_live,
2600
+ ) = await asyncio.gather(
2601
+ asyncio.to_thread(_read_main_tag),
2602
+ asyncio.to_thread(_npm_live),
2603
+ asyncio.to_thread(_pypi_live),
2604
+ asyncio.to_thread(_mcp_registry_live),
2605
+ asyncio.to_thread(_extension_live),
2606
+ asyncio.to_thread(_remote_mcp_live),
2607
+ )
2608
+ rel = _norm(main_tag)
2609
+
2610
+ # Aggregate usage (best-effort; shared across artifacts).
2611
+ usage = None
2612
+ try:
2613
+ usage = await _metrics.get_aggregate_metrics()
2614
+ except Exception:
2615
+ usage = None
2616
+
2617
+ # Static catalogue of publishable integrations + how to read each
2618
+ # one's LIVE version and where it is published.
2619
+ catalogue = [
2620
+ {
2621
+ "name": "npm (clsplusplus JS/TS SDK)",
2622
+ "live_version": npm_live,
2623
+ "publish_url": "https://www.npmjs.com/package/clsplusplus",
2624
+ "tracks_main_tag": True,
2625
+ },
2626
+ {
2627
+ "name": "PyPI (clsplusplus Python SDK + server + MCP)",
2628
+ "live_version": pypi_live,
2629
+ "publish_url": "https://pypi.org/project/clsplusplus/",
2630
+ "tracks_main_tag": True,
2631
+ },
2632
+ {
2633
+ "name": "MCP Registry (io.github.rajamohan1950/cls-memory)",
2634
+ "live_version": mcp_registry_live,
2635
+ "publish_url": (
2636
+ "https://registry.modelcontextprotocol.io/v0/servers"
2637
+ "?search=io.github.rajamohan1950/cls-memory"
2638
+ ),
2639
+ "tracks_main_tag": True,
2640
+ },
2641
+ {
2642
+ "name": "Chrome Web Store (CLS++ Memory extension)",
2643
+ "live_version": extension_live,
2644
+ "publish_url": "https://chromewebstore.google.com/",
2645
+ # Extension is intentionally on its own 6.x track.
2646
+ "tracks_main_tag": False,
2647
+ },
2648
+ {
2649
+ "name": "Remote MCP server (clsplusplus.com/mcp)",
2650
+ "live_version": remote_mcp_live,
2651
+ "publish_url": "https://www.clsplusplus.com/mcp",
2652
+ # A running service tracks the deployed release.
2653
+ "tracks_main_tag": True,
2654
+ },
2655
+ ]
2656
+
2657
+ integrations = []
2658
+ for entry in catalogue:
2659
+ live = entry["live_version"]
2660
+ if entry["tracks_main_tag"]:
2661
+ in_sync = bool(live) and rel is not None and _norm(live) == rel
2662
+ else:
2663
+ # Decoupled track: "in sync" only means we could read it.
2664
+ in_sync = bool(live)
2665
+ integrations.append({
2666
+ "name": entry["name"],
2667
+ "live_version": live,
2668
+ "main_tag": main_tag,
2669
+ "in_sync": in_sync,
2670
+ "publish_url": entry["publish_url"],
2671
+ "usage": usage,
2672
+ })
2673
+
2674
+ return {"main_tag": main_tag, "integrations": integrations}
2675
+ except Exception as e:
2676
+ # Defensive: this endpoint must never 500. Degrade to a fully
2677
+ # "unknown" report so the dashboard always renders.
2678
+ logger.error("Admin integrations error: %s: %s", type(e).__name__, e)
2679
+ tag = "unknown"
2680
+ try:
2681
+ tag = _read_main_tag()
2682
+ except Exception:
2683
+ pass
2684
+ return {
2685
+ "main_tag": tag,
2686
+ "integrations": [],
2687
+ "error": "integration data degraded",
2688
+ }
2689
+
2433
2690
  def _require_admin(request: Request):
2434
2691
  """Helper to enforce admin access on endpoints."""
2435
2692
  if not getattr(request.state, "is_admin", False):
@@ -114,6 +114,14 @@ class Settings(BaseSettings):
114
114
  # Embeddings (used by L1/L2/L3 tiers, not by phase engine)
115
115
  embedding_model: str = "all-MiniLM-L6-v2"
116
116
  embedding_dim: int = 384
117
+ # Embedding backend. "sentence-transformers" (default) keeps the local
118
+ # all-MiniLM-L6-v2 behavior unchanged. Set CLS_EMBEDDING_PROVIDER=openai to
119
+ # use the OpenAI embeddings API instead — openai is lazy-imported and is NOT
120
+ # a hard dependency; if it's missing the provider falls back to
121
+ # sentence-transformers. openai_api_key is read from the existing setting.
122
+ embedding_provider: str = "sentence-transformers" # CLS_EMBEDDING_PROVIDER
123
+ openai_embedding_model: str = "text-embedding-3-small" # CLS_OPENAI_EMBEDDING_MODEL
124
+ openai_embedding_dim: int = 1536 # CLS_OPENAI_EMBEDDING_DIM
117
125
 
118
126
  # Plasticity coefficients (α, β, γ, λ, δ)
119
127
  alpha_salience: float = 1.0
@@ -136,6 +144,11 @@ class Settings(BaseSettings):
136
144
  similarity_threshold: float = 0.7
137
145
  conflict_threshold: float = 0.3
138
146
  quorum_threshold: float = 0.8
147
+ # Belief-revision-on-write: when ON, the normal write() path routes new
148
+ # facts through the ReconsolidationGate to detect contradictions against
149
+ # existing same-namespace items before storing. OFF by default so the
150
+ # current write behavior is unchanged.
151
+ belief_revision_on_write: bool = False # CLS_BELIEF_REVISION_ON_WRITE
139
152
 
140
153
  # Working buffer
141
154
  l0_capacity_tokens: int = 4096
@@ -0,0 +1,173 @@
1
+ """Embedding service for CLS++."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+ from clsplusplus.config import Settings
9
+ from clsplusplus.models import MemoryItem
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BaseEmbeddingProvider:
15
+ """Interface for embedding backends.
16
+
17
+ A provider turns text into dense vectors. Implementations are lazy: the
18
+ underlying model/client is loaded on first use, never at construction.
19
+ """
20
+
21
+ name: str = "base"
22
+
23
+ @property
24
+ def dim(self) -> int:
25
+ """Dimensionality of the vectors this provider returns."""
26
+ raise NotImplementedError
27
+
28
+ def embed(self, texts: list[str]) -> list[list[float]]:
29
+ """Embed a batch of texts. Returns one vector per input text.
30
+
31
+ On failure (model/client unavailable) returns empty vectors so callers
32
+ never break — same contract the service has always honored.
33
+ """
34
+ raise NotImplementedError
35
+
36
+
37
+ class SentenceTransformerProvider(BaseEmbeddingProvider):
38
+ """Local sentence-transformers backend (default, unchanged behavior).
39
+
40
+ Wraps the original all-MiniLM-L6-v2 logic: lazy model load, returns empty
41
+ vectors when sentence-transformers is not installed.
42
+ """
43
+
44
+ name = "sentence-transformers"
45
+
46
+ def __init__(self, settings: Settings):
47
+ self.settings = settings
48
+ self._model = None
49
+
50
+ @property
51
+ def model(self):
52
+ """Lazy load the embedding model."""
53
+ if self._model is None:
54
+ try:
55
+ from sentence_transformers import SentenceTransformer
56
+ self._model = SentenceTransformer(self.settings.embedding_model)
57
+ except ImportError:
58
+ self._model = False # sentinel: unavailable
59
+ return self._model
60
+
61
+ @property
62
+ def dim(self) -> int:
63
+ return self.settings.embedding_dim
64
+
65
+ def embed(self, texts: list[str]) -> list[list[float]]:
66
+ if self.model is False:
67
+ return [[] for _ in texts]
68
+ return self.model.encode(texts, convert_to_numpy=True).tolist()
69
+
70
+
71
+ class OpenAIProvider(BaseEmbeddingProvider):
72
+ """OpenAI embeddings backend.
73
+
74
+ Selected via CLS_EMBEDDING_PROVIDER=openai. openai is lazy-imported and is
75
+ NOT a hard dependency. If the client/key is unavailable the embed call
76
+ returns empty vectors; selection-time fallback to sentence-transformers is
77
+ handled by the service factory.
78
+ """
79
+
80
+ name = "openai"
81
+
82
+ def __init__(self, settings: Settings):
83
+ self.settings = settings
84
+ self._client = None
85
+
86
+ @property
87
+ def client(self):
88
+ """Lazy build the OpenAI client."""
89
+ if self._client is None:
90
+ try:
91
+ from openai import OpenAI
92
+ self._client = OpenAI(api_key=self.settings.openai_api_key)
93
+ except ImportError:
94
+ self._client = False # sentinel: unavailable
95
+ return self._client
96
+
97
+ @property
98
+ def dim(self) -> int:
99
+ return self.settings.openai_embedding_dim
100
+
101
+ def embed(self, texts: list[str]) -> list[list[float]]:
102
+ if self.client is False:
103
+ return [[] for _ in texts]
104
+ try:
105
+ resp = self.client.embeddings.create(
106
+ model=self.settings.openai_embedding_model,
107
+ input=texts,
108
+ )
109
+ return [d.embedding for d in resp.data]
110
+ except Exception as e: # network / auth / quota — never break the caller
111
+ logger.warning("OpenAI embedding failed (returning empty vectors): %s", e)
112
+ return [[] for _ in texts]
113
+
114
+
115
+ def _build_provider(settings: Settings) -> BaseEmbeddingProvider:
116
+ """Select the embedding provider from settings.
117
+
118
+ Default is sentence-transformers. CLS_EMBEDDING_PROVIDER=openai selects
119
+ OpenAI, but if the openai package is not installed we warn and fall back to
120
+ sentence-transformers so behavior degrades gracefully.
121
+ """
122
+ provider = (settings.embedding_provider or "").strip().lower()
123
+ if provider == "openai":
124
+ try:
125
+ import openai # noqa: F401
126
+ except ImportError:
127
+ logger.warning(
128
+ "CLS_EMBEDDING_PROVIDER=openai but the openai package is not "
129
+ "installed; falling back to sentence-transformers."
130
+ )
131
+ return SentenceTransformerProvider(settings)
132
+ return OpenAIProvider(settings)
133
+ return SentenceTransformerProvider(settings)
134
+
135
+
136
+ class EmbeddingService:
137
+ """Produces embeddings for memory items."""
138
+
139
+ def __init__(self, settings: Optional[Settings] = None):
140
+ self.settings = settings or Settings()
141
+ self.provider = _build_provider(self.settings)
142
+
143
+ @property
144
+ def model(self):
145
+ """Backward-compat accessor for the underlying sentence-transformers model.
146
+
147
+ Returns None for non-sentence-transformers providers.
148
+ """
149
+ return getattr(self.provider, "model", None)
150
+
151
+ def embed(self, text: str) -> list[float]:
152
+ """Embed a single text. Returns empty list if provider unavailable."""
153
+ out = self.provider.embed([text])
154
+ return out[0] if out else []
155
+
156
+ def embed_batch(self, texts: list[str]) -> list[list[float]]:
157
+ """Embed multiple texts. Returns empty lists if provider unavailable."""
158
+ if not texts:
159
+ return []
160
+ return self.provider.embed(texts)
161
+
162
+ def embed_item(self, item: MemoryItem) -> MemoryItem:
163
+ """Add embedding to a memory item."""
164
+ if not item.embedding:
165
+ item.embedding = self.embed(item.text)
166
+ return item
167
+
168
+ @staticmethod
169
+ def cosine_similarity(a: list[float], b: list[float]) -> float:
170
+ """Compute cosine similarity between two vectors."""
171
+ va = np.array(a)
172
+ vb = np.array(b)
173
+ return float(np.dot(va, vb) / (np.linalg.norm(va) * np.linalg.norm(vb) + 1e-9))
@@ -0,0 +1,46 @@
1
+ """Third-party framework integrations for CLS++.
2
+
3
+ Each submodule adapts the CLS++ memory layer to a specific agent framework so
4
+ that framework users can plug CLS++ in as their persistent memory backend
5
+ without touching the HTTP client directly.
6
+
7
+ Currently provided:
8
+
9
+ clsplusplus.integrations.crewai.CLSMemoryStorage
10
+ Implements CrewAI's ``crewai.memory.storage.interface.Storage``
11
+ contract (``save`` / ``search`` / ``reset``), delegating to the CLS++
12
+ ``Brain`` client. Wire it into a Crew via ``external_memory=``.
13
+
14
+ clsplusplus.integrations.langgraph.CLSMemoryStore
15
+ Subclasses LangGraph's ``langgraph.store.base.BaseStore`` (the
16
+ long-term-memory ``store=`` you pass to ``graph.compile``), delegating
17
+ to the CLS++ ``Brain`` client. Requires ``pip install
18
+ clsplusplus[langgraph]``.
19
+
20
+ Integrations keep their framework imports lazy: importing ``clsplusplus`` (or
21
+ ``clsplusplus.integrations``) never requires the framework to be installed.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ __all__ = ["CLSMemoryStorage", "CLSMemoryStore", "CLSMemoryBlock", "CLSMemory"]
27
+
28
+
29
+ def __getattr__(name: str):
30
+ if name == "CLSMemoryStorage":
31
+ from clsplusplus.integrations.crewai import CLSMemoryStorage
32
+
33
+ return CLSMemoryStorage
34
+ if name == "CLSMemoryStore":
35
+ from clsplusplus.integrations.langgraph import CLSMemoryStore
36
+
37
+ return CLSMemoryStore
38
+ if name == "CLSMemoryBlock":
39
+ from clsplusplus.integrations.llamaindex import CLSMemoryBlock
40
+
41
+ return CLSMemoryBlock
42
+ if name == "CLSMemory":
43
+ from clsplusplus.integrations.autogen import CLSMemory
44
+
45
+ return CLSMemory
46
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")