server: reconcile ai_sessions persistence to Chunk 9 AISession shape

Reshape the ai_sessions table (migration 003) and mapping/routes to the
full AISession contract: title, kind, notes, created_at, last_message_at;
drop legacy role/model/settings. toAISession now maps the new columns and
routes create/patch/messages handlers use the new shape.
This commit is contained in:
Schwitzz
2026-10-29 15:03:32 -05:00
parent fc8eaa86c3
commit a1b5587fcb
15 changed files with 181 additions and 60 deletions
+35 -17
View File
@@ -92,11 +92,14 @@ const beatCreate = z.object({
});
const aiSessionCreate = z.object({
id: z.string().optional(),
role: z.string().min(1),
model: z.string().min(1),
settings: z.record(z.unknown()).default({}),
messages: z.array(z.any()).optional(),
suggestions: z.array(z.any()).optional(),
title: z.string().default(""),
kind: z.enum(["session", "agent"]).default("session"),
messages: z.array(z.any()).default([]),
suggestions: z.array(z.any()).default([]),
notes: z.array(z.any()).default([]),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
lastMessageAt: z.string().nullable().optional(),
});
const aiMessageAppend = z.object({ role: z.string(), content: z.string() });
const aiSuggestionAdd = z.object({ kind: z.string(), text: z.string() });
@@ -565,14 +568,15 @@ export function registerRoutes(app: FastifyInstance): void {
const sid = body.id ?? ulid();
const t = now();
db.prepare(
`INSERT INTO ai_sessions (id, project_id, role, model, settings, messages, suggestions, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`INSERT INTO ai_sessions (id, project_id, title, kind, messages, suggestions, notes, created_at, updated_at, last_message_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
project_id = excluded.project_id, role = excluded.role, model = excluded.model,
settings = excluded.settings, messages = excluded.messages, suggestions = excluded.suggestions,
updated_at = excluded.updated_at`,
).run(sid, id, body.role, body.model, JSON.stringify(body.settings),
JSON.stringify(body.messages ?? []), JSON.stringify(body.suggestions ?? []), t);
title = excluded.title, kind = excluded.kind,
messages = excluded.messages, suggestions = excluded.suggestions, notes = excluded.notes,
updated_at = excluded.updated_at, last_message_at = excluded.last_message_at`,
).run(sid, id, body.title, body.kind, JSON.stringify(body.messages),
JSON.stringify(body.suggestions), JSON.stringify(body.notes),
body.createdAt ?? t, t, body.lastMessageAt ?? null);
const row = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(sid) as AISessionRow;
return { session: toAISession(row) };
});
@@ -590,11 +594,25 @@ export function registerRoutes(app: FastifyInstance): void {
const row = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow | undefined;
if (!row) throw app.httpErrors.create(404, { error: "not_found" });
requireProjectAccess(app, req, row.project_id, true);
const body = z.object({ settings: z.record(z.unknown()).optional(), model: z.string().optional() }).parse(req.body);
const settings = body.settings ? JSON.stringify(body.settings) : row.settings;
const model = body.model ?? row.model;
const body = z.object({
title: z.string().optional(),
kind: z.enum(["session", "agent"]).optional(),
messages: z.array(z.any()).optional(),
suggestions: z.array(z.any()).optional(),
notes: z.array(z.any()).optional(),
lastMessageAt: z.string().nullable().optional(),
}).parse(req.body);
const t = now();
db.prepare("UPDATE ai_sessions SET settings = ?, model = ?, updated_at = ? WHERE id = ?").run(settings, model, t, id);
db.prepare(
`UPDATE ai_sessions SET title = ?, kind = ?, messages = ?, suggestions = ?, notes = ?, last_message_at = ?, updated_at = ? WHERE id = ?`
).run(
body.title ?? row.title, body.kind ?? row.kind,
JSON.stringify(body.messages ?? JSON.parse(row.messages)),
JSON.stringify(body.suggestions ?? JSON.parse(row.suggestions)),
JSON.stringify(body.notes ?? JSON.parse(row.notes)),
body.lastMessageAt ?? row.last_message_at,
t, id,
);
const updated = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow;
return { session: toAISession(updated) };
});
@@ -617,7 +635,7 @@ export function registerRoutes(app: FastifyInstance): void {
const messages = JSON.parse(row.messages) as any[];
messages.push({ role, content, at: now() });
const t = now();
db.prepare("UPDATE ai_sessions SET messages = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(messages), t, id);
db.prepare("UPDATE ai_sessions SET messages = ?, last_message_at = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(messages), t, t, id);
const updated = db.prepare("SELECT * FROM ai_sessions WHERE id = ?").get(id) as AISessionRow;
return { session: toAISession(updated) };
});