From 7f066ab7f367e54254c2661c231acf2d8643fd68 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Thu, 2 Jul 2026 23:41:26 +0100 Subject: [PATCH] Revert "feat: add conversation schemas (#154)" This reverts commit be8ad24d7dedaa068f444c74378354f85e400513. --- apps/freya-backend/src/conversations/http.ts | 140 ++++-------------- .../src/conversations/conversation-list.tsx | 3 +- .../src/conversations/conversations.ts | 21 +++ .../freya-client/src/conversations/queries.ts | 5 +- packages/freya-core/src/conversation.test.ts | 99 ------------- packages/freya-core/src/conversation.ts | 129 ---------------- packages/freya-core/src/index.ts | 11 -- 7 files changed, 54 insertions(+), 354 deletions(-) create mode 100644 apps/freya-client/src/conversations/conversations.ts diff --git a/apps/freya-backend/src/conversations/http.ts b/apps/freya-backend/src/conversations/http.ts index 34b7e32..5cc8d86 100644 --- a/apps/freya-backend/src/conversations/http.ts +++ b/apps/freya-backend/src/conversations/http.ts @@ -1,24 +1,14 @@ -import { - AssistantMessagePayload, - AttachmentPayload, - ConversationEntryKind, - ConversationEntryVisibility, - ContextSummaryPayload, - GenericObjectPayload, - ToolCallPayload, - ToolResultPayload, - UserMessagePayload, - type Conversation, - type ConversationEntry, -} from "@freya/core" -import { type } from "arktype" import type { Context, Hono } from "hono" + +import { ConversationEntryVisibility } from "@freya/core" +import { type } from "arktype" import { createMiddleware } from "hono/factory" import type { AuthSessionMiddleware } from "../auth/session-middleware.ts" import type { Database } from "../db/index.ts" +import type { ConversationRow } from "./storage.ts" + import { ConversationNotFoundError } from "./errors.ts" -import type { ConversationEntryRow, ConversationRow } from "./storage.ts" import { conversations } from "./storage.ts" /** Hono environment populated by the conversations route middleware. */ @@ -28,20 +18,19 @@ type Env = { } } +/** Serialized conversation summary returned by the list endpoint. */ +interface ConversationSummaryResponse { + id: string + createdAt: string + updatedAt: string +} + /** Dependencies required to register conversation HTTP handlers. */ interface ConversationsHttpHandlersDeps { db: Database authSessionMiddleware: AuthSessionMiddleware } -interface ListConversationsResponse { - conversations: Conversation[] -} - -interface ListConversationEntriesResponse { - entries: ConversationEntry[] -} - const ConversationIdParam = type("string.uuid") export function registerConversationsHttpHandlers( @@ -60,13 +49,12 @@ export function registerConversationsHttpHandlers( async function handleListConversations(c: Context) { const user = c.get("user")! const db = c.get("db") - const response: ListConversationsResponse = { + + return c.json({ conversations: (await conversations(db, user.id).listConversations()).map( serializeConversation, ), - } - - return c.json(response) + }) } async function handleListEntries(c: Context) { @@ -85,11 +73,20 @@ async function handleListEntries(c: Context) { const entries = await conversations(db, user.id).listEntries(parsedConversationId, { visibility: ConversationEntryVisibility.UserVisible, }) - const response: ListConversationEntriesResponse = { - entries: entries.map(serializeConversationEntry), - } - return c.json(response) + return c.json({ + entries: entries.map((row) => ({ + id: row.id, + conversationId: row.conversationId, + sequence: row.sequence, + kind: row.kind, + visibility: row.visibility, + fileId: row.fileId, + payload: row.payload, + metadata: row.metadata, + createdAt: row.createdAt.toISOString(), + })), + }) } catch (err) { if (err instanceof ConversationNotFoundError) { return c.json({ error: "Conversation not found" }, 404) @@ -98,89 +95,10 @@ async function handleListEntries(c: Context) { } } -function serializeConversation(row: ConversationRow): Conversation { +function serializeConversation(row: ConversationRow): ConversationSummaryResponse { return { id: row.id, createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), } } - -function serializeConversationEntry(row: ConversationEntryRow): ConversationEntry { - const base = { - id: row.id, - conversationId: row.conversationId, - sequence: row.sequence, - visibility: row.visibility, - metadata: row.metadata, - createdAt: row.createdAt.toISOString(), - } - - switch (row.kind) { - case ConversationEntryKind.UserMessage: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: UserMessagePayload.assert(row.payload), - } - case ConversationEntryKind.AssistantMessage: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: AssistantMessagePayload.assert(row.payload), - } - case ConversationEntryKind.Attachment: - return { - ...base, - kind: row.kind, - fileId: requireFileId(row), - payload: AttachmentPayload.assert(row.payload), - } - case ConversationEntryKind.ToolCall: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: ToolCallPayload.assert(row.payload), - } - case ConversationEntryKind.ToolResult: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: ToolResultPayload.assert(row.payload), - } - case ConversationEntryKind.ContextSummary: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: ContextSummaryPayload.assert(row.payload), - } - case ConversationEntryKind.SystemNote: - return { - ...base, - kind: row.kind, - fileId: nullFileId(row), - payload: GenericObjectPayload.assert(row.payload), - } - } -} - -function requireFileId(row: ConversationEntryRow): string { - if (!row.fileId) { - throw new Error(`Conversation attachment entry "${row.id}" is missing a file id`) - } - - return row.fileId -} - -function nullFileId(row: ConversationEntryRow): null { - if (row.fileId !== null) { - throw new Error(`Conversation entry "${row.id}" unexpectedly references a file`) - } - - return null -} diff --git a/apps/freya-client/src/conversations/conversation-list.tsx b/apps/freya-client/src/conversations/conversation-list.tsx index 5614a6d..1efa463 100644 --- a/apps/freya-client/src/conversations/conversation-list.tsx +++ b/apps/freya-client/src/conversations/conversation-list.tsx @@ -1,4 +1,4 @@ -import { AssistantMessagePayload, ConversationEntry, UserMessagePayload } from "@freya/core" +import { AssistantMessagePayload, UserMessagePayload } from "@freya/core" import { FlashList } from "@shopify/flash-list" import { useQuery } from "@tanstack/react-query" import { View, ViewStyle } from "react-native" @@ -6,6 +6,7 @@ import tw from "twrnc" import { SansSerifText } from "@/components/ui/sans-serif-text" +import { ConversationEntry } from "./conversations" import { useListConversationEntriesQuery, useDefaultConversationQuery } from "./queries" type ConversationListProps = Omit< diff --git a/apps/freya-client/src/conversations/conversations.ts b/apps/freya-client/src/conversations/conversations.ts new file mode 100644 index 0000000..2c17f2a --- /dev/null +++ b/apps/freya-client/src/conversations/conversations.ts @@ -0,0 +1,21 @@ +import { + ConversationEntryKind, + ConversationEntryPayload, + ConversationEntryVisibility, +} from "@freya/core" +import { type } from "arktype" + +export const Conversation = type({ + id: "string.uuid", + createdAt: "string.date.iso", + updatedAt: "string.date.iso", +}) + +export const ConversationEntry = type({ + id: "string.uuid", + sequence: "number", + kind: type.enumerated(...Object.values(ConversationEntryKind)), + visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), + fileId: "string | null", + payload: ConversationEntryPayload, +}) diff --git a/apps/freya-client/src/conversations/queries.ts b/apps/freya-client/src/conversations/queries.ts index 654bff4..e140a1e 100644 --- a/apps/freya-client/src/conversations/queries.ts +++ b/apps/freya-client/src/conversations/queries.ts @@ -1,16 +1,15 @@ -import { Conversation, ConversationEntry } from "@freya/core" import { queryOptions, skipToken } from "@tanstack/react-query" import { type } from "arktype" import { useApiClient } from "@/api/client" +import { Conversation, ConversationEntry } from "./conversations" + const ListConversationsResponse = type({ - "+": "reject", conversations: Conversation.array(), }) const ConversationEntriesResponse = type({ - "+": "reject", entries: ConversationEntry.array(), }) diff --git a/packages/freya-core/src/conversation.test.ts b/packages/freya-core/src/conversation.test.ts index 96b69fc..1297e05 100644 --- a/packages/freya-core/src/conversation.test.ts +++ b/packages/freya-core/src/conversation.test.ts @@ -4,11 +4,7 @@ import { AttachmentType, AttachmentPayload, ContextSummaryPayload, - Conversation, - ConversationEntry, - ConversationEntryKind, ConversationEntryMetadata, - ConversationEntryVisibility, GenericObjectPayload, UserMessagePayload, } from "./conversation" @@ -147,99 +143,4 @@ describe("conversation entry schemas", () => { }), ).toThrow() }) - - test("parses conversation summaries", () => { - const conversation = Conversation.assert({ - id: "11111111-1111-4111-8111-111111111111", - createdAt: "2026-06-17T09:30:00.000Z", - updatedAt: "2026-06-17T09:35:00.000Z", - }) - - expect(conversation.id).toBe("11111111-1111-4111-8111-111111111111") - }) - - test("parses kind-specific conversation entries", () => { - const userMessageEntry = ConversationEntry.assert({ - id: "22222222-2222-4222-8222-222222222222", - conversationId: "11111111-1111-4111-8111-111111111111", - sequence: 1, - kind: ConversationEntryKind.UserMessage, - visibility: ConversationEntryVisibility.UserVisible, - fileId: null, - payload: { - role: "user", - parts: [{ type: "text", text: "hello" }], - }, - metadata: {}, - createdAt: "2026-06-17T09:30:00.000Z", - }) - const attachmentEntry = ConversationEntry.assert({ - id: "33333333-3333-4333-8333-333333333333", - conversationId: "11111111-1111-4111-8111-111111111111", - sequence: 2, - kind: ConversationEntryKind.Attachment, - visibility: ConversationEntryVisibility.UserVisible, - fileId: "44444444-4444-4444-8444-444444444444", - payload: { - role: "user", - name: "photo.png", - mimeType: "image/png", - attachmentType: AttachmentType.Image, - }, - metadata: {}, - createdAt: "2026-06-17T09:31:00.000Z", - }) - - expect(userMessageEntry.kind).toBe(ConversationEntryKind.UserMessage) - expect(attachmentEntry.kind).toBe(ConversationEntryKind.Attachment) - }) - - test("rejects conversation entries whose payload does not match the kind", () => { - expect(() => - ConversationEntry.assert({ - id: "22222222-2222-4222-8222-222222222222", - conversationId: "11111111-1111-4111-8111-111111111111", - sequence: 1, - kind: ConversationEntryKind.UserMessage, - visibility: ConversationEntryVisibility.UserVisible, - fileId: null, - payload: { - role: "assistant", - parts: [{ type: "text", text: "hello" }], - }, - metadata: {}, - createdAt: "2026-06-17T09:30:00.000Z", - }), - ).toThrow() - }) - - test("rejects serialized conversations with extra fields", () => { - expect(() => - Conversation.assert({ - id: "11111111-1111-4111-8111-111111111111", - createdAt: "2026-06-17T09:30:00.000Z", - updatedAt: "2026-06-17T09:35:00.000Z", - title: "not yet part of the schema", - }), - ).toThrow() - }) - - test("rejects file ids on non-attachment entries", () => { - expect(() => - ConversationEntry.assert({ - id: "22222222-2222-4222-8222-222222222222", - conversationId: "11111111-1111-4111-8111-111111111111", - sequence: 1, - kind: ConversationEntryKind.UserMessage, - visibility: ConversationEntryVisibility.UserVisible, - fileId: "44444444-4444-4444-8444-444444444444", - payload: { - role: "user", - parts: [{ type: "text", text: "hello" }], - }, - metadata: {}, - createdAt: "2026-06-17T09:30:00.000Z", - }), - ).toThrow() - }) }) diff --git a/packages/freya-core/src/conversation.ts b/packages/freya-core/src/conversation.ts index db9574d..ed6fdeb 100644 --- a/packages/freya-core/src/conversation.ts +++ b/packages/freya-core/src/conversation.ts @@ -146,19 +146,6 @@ export const ConversationEntryMetadata = type({ /** Metadata bag attached to a conversation entry. */ export type ConversationEntryMetadata = typeof ConversationEntryMetadata.infer -export const ToolCallPayload = type({ - toolName: "string", -}) - -export type ToolCallPayload = typeof ToolCallPayload.infer - -export const ToolResultPayload = type({ - toolName: "string", - ok: "boolean", -}) - -export type ToolResultPayload = typeof ToolResultPayload.infer - /** Generic object payload used by operational entries. */ export const GenericObjectPayload = type("Record") @@ -170,8 +157,6 @@ export const ConversationEntryPayload = type.or( AssistantMessagePayload, AttachmentPayload, ContextSummaryPayload, - ToolCallPayload, - ToolResultPayload, GenericObjectPayload, ) @@ -181,118 +166,4 @@ export type ConversationEntryPayload = | AssistantMessagePayload | AttachmentPayload | ContextSummaryPayload - | ToolCallPayload - | ToolResultPayload | GenericObjectPayload - -export const Conversation = type({ - "+": "reject", - id: "string.uuid", - createdAt: "string.date.iso", - updatedAt: "string.date.iso", -}) - -export type Conversation = typeof Conversation.infer - -export const UserMessageConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'user_message'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: UserMessagePayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const AssistantMessageConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'assistant_message'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: AssistantMessagePayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const AttachmentConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'attachment'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "string.uuid", - payload: AttachmentPayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const ToolCallConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'tool_call'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: ToolCallPayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const ToolResultConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'tool_result'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: ToolResultPayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const ContextSummaryConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'context_summary'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: ContextSummaryPayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const SystemNoteConversationEntry = type({ - "+": "reject", - id: "string.uuid", - conversationId: "string.uuid", - sequence: "number.integer >= 1", - kind: "'system_note'", - visibility: type.enumerated(...Object.values(ConversationEntryVisibility)), - fileId: "null", - payload: GenericObjectPayload, - metadata: ConversationEntryMetadata, - createdAt: "string.date.iso", -}) - -export const ConversationEntry = type.or( - UserMessageConversationEntry, - AssistantMessageConversationEntry, - AttachmentConversationEntry, - ToolCallConversationEntry, - ToolResultConversationEntry, - ContextSummaryConversationEntry, - SystemNoteConversationEntry, -) - -export type ConversationEntry = typeof ConversationEntry.infer diff --git a/packages/freya-core/src/index.ts b/packages/freya-core/src/index.ts index 59e8a78..66cf5a2 100644 --- a/packages/freya-core/src/index.ts +++ b/packages/freya-core/src/index.ts @@ -9,15 +9,10 @@ export { UnknownActionError } from "./action"; // Conversation export { AssistantMessagePayload, - AssistantMessageConversationEntry, AttachmentPayload, - AttachmentConversationEntry, AttachmentType, - ContextSummaryConversationEntry, ContextSummary, ContextSummaryPayload, - Conversation, - ConversationEntry, ConversationEntryKind, ConversationEntryMetadata, ConversationEntryVisibility, @@ -25,15 +20,9 @@ export { JsonMessagePart, MessagePart, ModelRunMetadata, - SystemNoteConversationEntry, TextMessagePart, - ToolCallConversationEntry, - ToolCallPayload, - ToolResultConversationEntry, - ToolResultPayload, UserMessagePayload, ConversationEntryPayload, - UserMessageConversationEntry, } from "./conversation"; // Feed