Assistant: persist the chat thread in Postgres

Back MAF's opt-in AG-UI snapshot persistence with a DB store so the
conversation survives page reloads and restarts:

- ChatThreadSnapshot: one upserted row per (scope, thread_id), JSONB
  messages/state/interrupt (migration 794baf50635b)
- PostgresSnapshotStore implements the AGUIThreadSnapshotStore protocol.
  It opens its own sessions (MAF saves during SSE streaming, after the
  request middleware has closed the ContextVar session) and sanitizes on
  save: camelCase toolCalls/toolCallId (the @ag-ui/client Zod schema
  strips snake_case, which would lose cards on replay), drop BFF ctx-*
  context messages, dedupe MAF's re-recorded multi-run turns, and drop
  orphaned tool results
- Endpoint mount gains snapshot_store + a constant scope resolver
  (single-user app; auth already enforced by agent_auth_and_session)
- DELETE /api/v1/chat/thread resets the conversation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-07-04 18:09:55 -06:00
parent 0e03284c95
commit 6bce5539f7
8 changed files with 500 additions and 2 deletions

View File

@@ -345,6 +345,23 @@ export interface paths {
patch: operations["update_category_api_v1_categories__category_id__patch"];
trace?: never;
};
"/api/v1/chat/thread": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Clear Thread */
delete: operations["clear_thread_api_v1_chat_thread_delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/exchange-rate/": {
parameters: {
query?: never;
@@ -1200,6 +1217,11 @@ export interface components {
/** Name */
name?: string | null;
};
/** ClearThreadResponse */
ClearThreadResponse: {
/** Cleared */
cleared: boolean;
};
/**
* Currency
* @enum {string}
@@ -3023,6 +3045,39 @@ export interface operations {
};
};
};
clear_thread_api_v1_chat_thread_delete: {
parameters: {
query?: never;
header?: {
authorization?: string | null;
};
path?: never;
cookie?: {
ws_token?: string | null;
};
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ClearThreadResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
current_rate_api_v1_exchange_rate__get: {
parameters: {
query?: never;