feat: add conversation schemas (#155)

This commit is contained in:
2026-07-02 23:54:08 +01:00
committed by GitHub
parent 952f8e4fb0
commit 9aaefda216
4 changed files with 328 additions and 26 deletions

View File

@@ -4,7 +4,11 @@ import {
AttachmentType,
AttachmentPayload,
ContextSummaryPayload,
Conversation,
ConversationEntry,
ConversationEntryKind,
ConversationEntryMetadata,
ConversationEntryVisibility,
GenericObjectPayload,
UserMessagePayload,
} from "./conversation"
@@ -143,4 +147,99 @@ 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()
})
})

View File

@@ -159,3 +159,115 @@ export type ConversationEntryPayload =
| AttachmentPayload
| ContextSummaryPayload
| 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: GenericObjectPayload,
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: GenericObjectPayload,
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

View File

@@ -10,10 +10,15 @@ export { UnknownActionError } from "./action"
export type { ConversationEntryPayload } from "./conversation"
export {
AssistantMessagePayload,
AssistantMessageConversationEntry,
AttachmentPayload,
AttachmentConversationEntry,
AttachmentType,
ContextSummary,
ContextSummaryConversationEntry,
ContextSummaryPayload,
Conversation,
ConversationEntry,
ConversationEntryKind,
ConversationEntryMetadata,
ConversationEntryVisibility,
@@ -21,8 +26,12 @@ export {
JsonMessagePart,
MessagePart,
ModelRunMetadata,
SystemNoteConversationEntry,
TextMessagePart,
ToolCallConversationEntry,
ToolResultConversationEntry,
UserMessagePayload,
UserMessageConversationEntry,
} from "./conversation"
// Feed