Skip to content

Commit e15ee8f

Browse files
feat: add PraisonAI↔aiui datastore adapter bridge (Phase 0)
- Create PraisonAI SessionStore → aiui BaseDataStore adapter - Bridge native PraisonAI session persistence into aiui dashboard - Wire adapter into ui_chat and claw default apps - Add comprehensive unit tests with mock store - Graceful fallback handling for missing dependencies - Protocol-driven design following AGENTS.md guidelines Fixes #1443 Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
1 parent fa90dea commit e15ee8f

File tree

4 files changed

+499
-0
lines changed

4 files changed

+499
-0
lines changed

src/praisonai/praisonai/claw/default_app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
import os
1111
import praisonaiui as aiui
1212

13+
# ── Wire PraisonAI native sessions into aiui dashboard ─────
14+
try:
15+
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
16+
aiui.set_datastore(PraisonAISessionDataStore())
17+
except ImportError:
18+
# aiui or praisonaiagents not available - sessions won't persist
19+
pass
20+
1321
# ── Dashboard style ─────────────────────────────────────────
1422
aiui.set_style("dashboard")
1523
aiui.set_pages([
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
"""Bridge praisonaiagents SessionStore → aiui BaseDataStore.
2+
3+
Allows any SessionStoreProtocol implementation (file/Redis/Mongo) to back
4+
the aiui dashboard's Sessions page. No Chainlit required.
5+
6+
This adapter enables PraisonAI's native session persistence to power
7+
the aiui dashboard, maintaining consistency with Core SDK storage.
8+
"""
9+
from __future__ import annotations
10+
11+
import uuid
12+
from typing import Any, Optional, Dict, List
13+
14+
try:
15+
from praisonaiui.datastore import BaseDataStore
16+
except ImportError:
17+
# Graceful fallback for when aiui is not available
18+
class BaseDataStore:
19+
"""Fallback BaseDataStore for when aiui is not installed."""
20+
pass
21+
22+
try:
23+
from praisonaiagents.session import (
24+
SessionStoreProtocol,
25+
get_hierarchical_session_store,
26+
)
27+
except ImportError:
28+
# Graceful fallback for when praisonaiagents is not available
29+
SessionStoreProtocol = None
30+
get_hierarchical_session_store = None
31+
32+
33+
class PraisonAISessionDataStore(BaseDataStore):
34+
"""Adapter that bridges SessionStoreProtocol → aiui BaseDataStore.
35+
36+
This allows any PraisonAI SessionStore implementation (file-based,
37+
Redis, MongoDB, etc.) to serve as the persistence layer for aiui
38+
dashboard sessions.
39+
40+
Args:
41+
store: SessionStoreProtocol implementation. If None, uses
42+
get_hierarchical_session_store() as default.
43+
44+
Example:
45+
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
46+
import praisonaiui as aiui
47+
48+
# Use default hierarchical store
49+
aiui.set_datastore(PraisonAISessionDataStore())
50+
51+
# Or provide custom store
52+
from my_project import MyRedisStore
53+
aiui.set_datastore(PraisonAISessionDataStore(MyRedisStore()))
54+
"""
55+
56+
def __init__(self, store: Optional[SessionStoreProtocol] = None):
57+
if SessionStoreProtocol is None:
58+
raise ImportError(
59+
"praisonaiagents is required to use PraisonAISessionDataStore. "
60+
"Install with: pip install praisonaiagents"
61+
)
62+
63+
if store is None and get_hierarchical_session_store is None:
64+
raise ImportError(
65+
"praisonaiagents.session.get_hierarchical_session_store is required. "
66+
"Install with: pip install praisonaiagents"
67+
)
68+
69+
self._store = store or get_hierarchical_session_store()
70+
71+
def _new_id(self) -> str:
72+
"""Generate a new unique session ID."""
73+
return str(uuid.uuid4())
74+
75+
async def list_sessions(self) -> List[Dict[str, Any]]:
76+
"""List all available sessions.
77+
78+
Note: SessionStoreProtocol doesn't expose a list_sessions method,
79+
so this is a limitation. For file-based stores, we could potentially
80+
scan the filesystem, but that would be implementation-specific.
81+
82+
For now, return empty list. Users will need to know their session IDs
83+
or create new sessions.
84+
"""
85+
# TODO: If needed, could add optional list_sessions to SessionStoreProtocol
86+
# For now, return empty - sessions are created on-demand
87+
return []
88+
89+
async def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
90+
"""Get a session by ID.
91+
92+
Args:
93+
session_id: Session identifier.
94+
95+
Returns:
96+
Session dict with 'id' and 'messages' fields, or None if not found.
97+
"""
98+
if not self._store.session_exists(session_id):
99+
return None
100+
101+
messages = self._store.get_chat_history(session_id)
102+
return {
103+
"id": session_id,
104+
"messages": messages,
105+
"metadata": {
106+
"created_via": "praisonai",
107+
"message_count": len(messages),
108+
},
109+
}
110+
111+
async def create_session(self, session_id: Optional[str] = None) -> Dict[str, Any]:
112+
"""Create a new session.
113+
114+
Args:
115+
session_id: Optional session ID. If None, generates a new UUID.
116+
117+
Returns:
118+
Session dict with 'id' and empty 'messages' list.
119+
"""
120+
sid = session_id or self._new_id()
121+
122+
# SessionStoreProtocol creates sessions lazily on first add_message,
123+
# so we don't need to explicitly create here
124+
return {
125+
"id": sid,
126+
"messages": [],
127+
"metadata": {
128+
"created_via": "praisonai",
129+
"message_count": 0,
130+
},
131+
}
132+
133+
async def delete_session(self, session_id: str) -> bool:
134+
"""Delete a session completely.
135+
136+
Args:
137+
session_id: Session identifier.
138+
139+
Returns:
140+
True if deleted successfully.
141+
"""
142+
return self._store.delete_session(session_id)
143+
144+
async def add_message(
145+
self,
146+
session_id: str,
147+
message: Dict[str, Any]
148+
) -> bool:
149+
"""Add a message to a session.
150+
151+
Args:
152+
session_id: Session identifier.
153+
message: Message dict with 'role', 'content', and optional 'metadata'.
154+
155+
Returns:
156+
True if added successfully.
157+
"""
158+
role = message.get("role", "user")
159+
content = message.get("content", "")
160+
metadata = message.get("metadata")
161+
162+
return self._store.add_message(
163+
session_id=session_id,
164+
role=role,
165+
content=content,
166+
metadata=metadata,
167+
)
168+
169+
async def get_messages(self, session_id: str) -> List[Dict[str, Any]]:
170+
"""Get all messages for a session.
171+
172+
Args:
173+
session_id: Session identifier.
174+
175+
Returns:
176+
List of message dicts in LLM-compatible format.
177+
"""
178+
return self._store.get_chat_history(session_id)
179+
180+
async def clear_session(self, session_id: str) -> bool:
181+
"""Clear all messages from a session (keep metadata).
182+
183+
Args:
184+
session_id: Session identifier.
185+
186+
Returns:
187+
True if cleared successfully.
188+
"""
189+
return self._store.clear_session(session_id)

src/praisonai/praisonai/ui_chat/default_app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414

1515
import praisonaiui as aiui
1616

17+
# ── Wire PraisonAI native sessions into aiui dashboard ─────
18+
try:
19+
from praisonai.ui._aiui_datastore import PraisonAISessionDataStore
20+
aiui.set_datastore(PraisonAISessionDataStore())
21+
except ImportError:
22+
# aiui or praisonaiagents not available - sessions won't persist
23+
pass
24+
1725
# ── Dashboard style, but no sidebar navigation ─────────────
1826
aiui.set_style("dashboard")
1927
aiui.set_dashboard(sidebar=False, page_header=False)

0 commit comments

Comments
 (0)