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

+38
View File
@@ -14,6 +14,8 @@ from pathlib import Path
PUBLIC_FIELDS = ('id', 'username', 'display_name', 'role', 'status', 'container_name', 'error')
SLUG = re.compile(r'[a-z][a-z0-9]*(?:-[a-z0-9]+)*\Z')
MODEL = re.compile(r'[!-~]{1,200}\Z')
THINKING_LEVELS = ('none', 'minimal', 'low', 'medium', 'high', 'xhigh')
def public_user(user):
@@ -74,6 +76,10 @@ class Accounts:
error TEXT, password_hash TEXT NOT NULL,
hermes_url TEXT, hermes_api_key TEXT, desired TEXT NOT NULL DEFAULT 'ready'
);
CREATE TABLE IF NOT EXISTS account_model_settings (
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
model TEXT NOT NULL, thinking_level TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS sessions (
hash TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at REAL NOT NULL
@@ -105,6 +111,38 @@ class Accounts:
row = self.db.execute('SELECT * FROM users WHERE id=?', (user_id,)).fetchone()
return dict(row) if row else None
def get_model_settings(self, user_id):
with self.lock:
# The join checks account availability and reads settings in one
# snapshot, including accounts that have not finished provisioning.
row = self.db.execute('''
SELECT COALESCE(s.model, 'auto') AS model,
COALESCE(s.thinking_level, 'none') AS thinking_level
FROM users u LEFT JOIN account_model_settings s ON s.user_id=u.id
WHERE u.id=? AND u.desired!='deleted'
''', (user_id,)).fetchone()
if row is None:
raise KeyError(user_id)
return dict(row)
def set_model_settings(self, user_id, model, thinking_level):
if not isinstance(model, str) or not MODEL.fullmatch(model):
raise ValueError('Model must contain 1–200 printable ASCII characters without whitespace')
if not isinstance(thinking_level, str) or thinking_level not in THINKING_LEVELS:
raise ValueError('Invalid thinking level')
with self.lock, self.db:
# Guard and upsert are one write statement: a delete on another
# connection cannot interleave between checking and storing.
cursor = self.db.execute('''
INSERT INTO account_model_settings(user_id,model,thinking_level)
SELECT id,?,? FROM users WHERE id=? AND desired!='deleted'
ON CONFLICT(user_id) DO UPDATE SET
model=excluded.model, thinking_level=excluded.thinking_level
''', (model, thinking_level, user_id))
if cursor.rowcount != 1:
raise KeyError(user_id)
return {'model': model, 'thinking_level': thinking_level}
def by_username(self, username):
with self.lock:
row = self.db.execute('SELECT * FROM users WHERE username=?', (username,)).fetchone()