feat: save account model and thinking settings for Hermes
Rebuild & Deploy / rebuild (push) Successful in 1m9s

This commit is contained in:
snowspeeder committed 2026-10-01 16:38:34 -04:00
1 parent a45569f8c1
commit 6cb4427210
13 files changed
+798 -16

No files matched your search

+43 -2
View File
@@ -206,7 +206,7 @@ class Lifecycle:
await asyncio.gather(*tasks, return_exceptions=True)
def create_app(settings=None, provisioner=None):
def create_app(settings=None, provisioner=None, model_catalog=None):
settings = settings or Settings()
@asynccontextmanager
@@ -222,6 +222,9 @@ def create_app(settings=None, provisioner=None):
if adapter is None:
from .provisioner import Provisioner as HermesProvisioner
adapter = HermesProvisioner()
from .model_catalog import ModelCatalog
catalog = model_catalog if model_catalog is not None else ModelCatalog()
app.state.model_catalog = catalog
app.state.lifecycle = Lifecycle(store, adapter)
# Revalidate even previously-ready containers after control restart.
for user in store.users():
@@ -234,6 +237,8 @@ def create_app(settings=None, provisioner=None):
loop.cancel()
await asyncio.gather(loop, return_exceptions=True)
await app.state.lifecycle.stop()
if hasattr(catalog, 'aclose'):
await catalog.aclose()
if hasattr(adapter, 'aclose'):
await adapter.aclose()
finally:
@@ -315,6 +320,41 @@ def create_app(settings=None, provisioner=None):
async def me(user=Depends(current_user)):
return {'user': user}
@app.get('/api/settings/model')
async def model_settings(user=Depends(current_user)):
try:
saved = await asyncio.to_thread(app.state.accounts.get_model_settings, user['id'])
except KeyError:
raise HTTPException(401, 'Account unavailable') from None
try:
models = await app.state.model_catalog.models()
available, message = True, ''
except Exception:
models, available = [], False
message = 'Model list unavailable. Your saved settings are unchanged; try again.'
return {'settings': saved, 'models': models, 'catalog_available': available,
'message': message, 'thinking_note': 'Thinking levels are requests. Some models may limit or ignore them.'}
@app.put('/api/settings/model')
async def save_model_settings(request: Request, user=Depends(current_user)):
body = await bounded_json(request)
if set(body) != {'model', 'thinking_level'}:
raise HTTPException(400, 'Only model and thinking_level are accepted')
try:
models = await app.state.model_catalog.models()
except Exception:
raise HTTPException(503, 'Model list unavailable. Settings were not changed; try again.') from None
selected = next((m for m in models if m['id'] == body.get('model')), None)
if selected is None or body.get('thinking_level') not in selected['thinking_levels']:
raise HTTPException(400, 'Choose an available model and thinking level')
try:
saved = await asyncio.to_thread(app.state.accounts.set_model_settings, user['id'], body['model'], body['thinking_level'])
except ValueError:
raise HTTPException(400, 'Invalid model settings') from None
except KeyError:
raise HTTPException(401, 'Account unavailable') from None
return {'settings': saved, 'message': 'Saved for your account. Applies to your next request.'}
@app.get('/api/admin/users')
async def list_users(user=Depends(administrator)):
return {'users': [public_user(u) for u in await asyncio.to_thread(app.state.accounts.users)]}
@@ -366,7 +406,8 @@ def create_app(settings=None, provisioner=None):
raise HTTPException(404, 'User not found')
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']}
return {'user': public_user(user), 'hermes_url': user['hermes_url'], 'hermes_api_key': user['hermes_api_key'],
'model_settings': app.state.accounts.get_model_settings(user['id'])}
@app.get('/internal/users/{user_id}', dependencies=[Depends(internal_authorized)])
async def internal_user(user_id: str):