feat: implement durable authenticated chat run coordinator
This commit is contained in:
1 parent
08b7e20be8
commit
550f7c2e91
6 files changed
+905
-3
No files matched your search
+32
-3
@@ -206,13 +206,25 @@ class Lifecycle:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
|
||||
def create_app(settings=None, provisioner=None, model_catalog=None):
|
||||
def create_app(settings=None, provisioner=None, model_catalog=None, chat_adapter=None, feature_provider=None):
|
||||
settings = settings or Settings()
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
store = Accounts(settings.db_path)
|
||||
app.state.accounts = store
|
||||
from .chat_store import ChatStore
|
||||
from .chat_runs import ChatRuns
|
||||
app.state.chat_store = await asyncio.to_thread(ChatStore, store)
|
||||
from .chat_features import FeatureStore, FeatureProvider
|
||||
from pathlib import Path
|
||||
feature_root = str(Path(settings.db_path).absolute().parent / 'chat-features')
|
||||
app.state.feature_store = await asyncio.to_thread(FeatureStore, store, feature_root)
|
||||
app.state.feature_provider = feature_provider or FeatureProvider(store)
|
||||
from .chat_features import create_feature_router
|
||||
app.include_router(create_feature_router(app.state.feature_store, app.state.feature_provider))
|
||||
app.state.chat_runs = await asyncio.to_thread(ChatRuns, store, app.state.chat_store, chat_adapter, app.state.feature_store)
|
||||
app.state.chat_runs.feature_provider = app.state.feature_provider
|
||||
try:
|
||||
if not store.users():
|
||||
if not settings.admin_password:
|
||||
@@ -231,9 +243,13 @@ def create_app(settings=None, provisioner=None, model_catalog=None):
|
||||
app.state.lifecycle.schedule(user['id'])
|
||||
app.state.dummy_hash = await asyncio.to_thread(password_hash, 'timing-only-password')
|
||||
loop = asyncio.create_task(app.state.lifecycle.loop(settings.reconcile_interval))
|
||||
await app.state.chat_runs.start()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await app.state.chat_runs.stop()
|
||||
if hasattr(app.state.feature_provider, 'aclose'):
|
||||
await app.state.feature_provider.aclose()
|
||||
loop.cancel()
|
||||
await asyncio.gather(loop, return_exceptions=True)
|
||||
await app.state.lifecycle.stop()
|
||||
@@ -407,6 +423,7 @@ def create_app(settings=None, provisioner=None, model_catalog=None):
|
||||
if user['status'] != 'ready' or not user['hermes_url'] or not user['hermes_api_key']:
|
||||
raise HTTPException(503, 'User Hermes is not ready')
|
||||
return {'user': public_user(user), 'hermes_url': user['hermes_url'], 'hermes_api_key': user['hermes_api_key'],
|
||||
'conversation_id': user.get('conversation_id'),
|
||||
'model_settings': app.state.accounts.get_model_settings(user['id'])}
|
||||
|
||||
@app.get('/internal/users/{user_id}', dependencies=[Depends(internal_authorized)])
|
||||
@@ -430,13 +447,21 @@ def create_app(settings=None, provisioner=None, model_catalog=None):
|
||||
raise HTTPException(400, 'Invalid room grant')
|
||||
if type(ttl) is not int or not 1 <= ttl <= 86400:
|
||||
raise HTTPException(400, 'Invalid room expiry')
|
||||
conversation_id = body.get('conversation_id')
|
||||
try:
|
||||
await asyncio.to_thread(app.state.accounts.grant_room, user_id, room, ttl, body.get('session_token'))
|
||||
if conversation_id is None:
|
||||
conversation = await asyncio.to_thread(app.state.chat_store.create_conversation, user_id)
|
||||
conversation_id = conversation['id']
|
||||
else:
|
||||
await asyncio.to_thread(app.state.chat_store.get_conversation, user_id, conversation_id)
|
||||
await asyncio.to_thread(app.state.accounts.grant_room, user_id, room, ttl, body.get('session_token'), conversation_id)
|
||||
except KeyError:
|
||||
raise HTTPException(404, 'Conversation not found') from None
|
||||
except ValueError:
|
||||
raise HTTPException(409, 'Account not ready') from None
|
||||
except sqlite3.IntegrityError:
|
||||
raise HTTPException(409, 'Room already granted') from None
|
||||
return {'room_name': room, 'user_id': user_id}
|
||||
return {'room_name': room, 'user_id': user_id, 'conversation_id': conversation_id}
|
||||
|
||||
@app.get('/internal/rooms/{room_name}', dependencies=[Depends(internal_authorized)])
|
||||
async def resolve_room(room_name: str):
|
||||
@@ -446,4 +471,8 @@ def create_app(settings=None, provisioner=None, model_catalog=None):
|
||||
async def health():
|
||||
return {'status': 'ok'}
|
||||
|
||||
from .chat_api import create_chat_router
|
||||
app.include_router(create_chat_router())
|
||||
from .chat_exports import create_export_router
|
||||
app.include_router(create_export_router())
|
||||
return app
|
||||
Reference in new issue
Block a user