From e6af1b7851d169fe60f11eb2dc9b8cf0fe86cf88 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Thu, 18 Jun 2026 20:47:36 +0100 Subject: [PATCH] refactor: move conversation types to core (#149) --- ...conversation-recording-query-agent.test.ts | 2 +- .../conversation-recording-query-agent.ts | 8 +- .../src/agent/pi-query-agent.test.ts | 2 +- .../freya-backend/src/agent/pi-query-agent.ts | 12 +++ .../src/agent/session-manager.test.ts | 2 +- .../src/agent/session-manager.ts | 13 +-- .../src/conversations/http.test.ts | 2 +- apps/freya-backend/src/conversations/http.ts | 5 +- .../src/conversations/storage.ts | 83 ++++++++++--------- apps/freya-backend/src/db/schema.ts | 15 ++-- .../src/session/user-session-manager.test.ts | 2 +- .../src/session/user-session.test.ts | 2 +- bun.lock | 1 + packages/freya-core/package.json | 3 +- .../freya-core/src/conversation.test.ts | 2 +- .../freya-core/src/conversation.ts | 45 +++++++--- packages/freya-core/src/index.ts | 19 +++++ 17 files changed, 143 insertions(+), 75 deletions(-) rename apps/freya-backend/src/conversations/types.test.ts => packages/freya-core/src/conversation.test.ts (99%) rename apps/freya-backend/src/conversations/types.ts => packages/freya-core/src/conversation.ts (61%) diff --git a/apps/freya-backend/src/agent/conversation-recording-query-agent.test.ts b/apps/freya-backend/src/agent/conversation-recording-query-agent.test.ts index f1611df..c6f51ad 100644 --- a/apps/freya-backend/src/agent/conversation-recording-query-agent.test.ts +++ b/apps/freya-backend/src/agent/conversation-recording-query-agent.test.ts @@ -1,3 +1,4 @@ +import { ConversationEntryKind } from "@freya/core" import { describe, expect, test } from "bun:test" import type { AppendConversationEntryInput } from "../conversations/storage.ts" @@ -6,7 +7,6 @@ import type { ConversationStorageEntry, } from "./conversation-recording-query-agent.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { ConversationRecordingQueryAgent } from "./conversation-recording-query-agent.ts" import { createQueryAgentEventListeners, diff --git a/apps/freya-backend/src/agent/conversation-recording-query-agent.ts b/apps/freya-backend/src/agent/conversation-recording-query-agent.ts index 041a15d..2d6677b 100644 --- a/apps/freya-backend/src/agent/conversation-recording-query-agent.ts +++ b/apps/freya-backend/src/agent/conversation-recording-query-agent.ts @@ -1,12 +1,13 @@ +import type { ConversationEntryMetadata } from "@freya/core" + +import { ConversationEntryKind } from "@freya/core" import { randomUUID } from "node:crypto" import type { AppendConversationEntryInput, ConversationEntryRow, } from "../conversations/storage.ts" -import type { ConversationEntryMetadata } from "../conversations/types.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { createQueryAgentEventListeners, QueryAgentEvent, @@ -19,6 +20,7 @@ import { type QueryAgentStreamEvent, } from "./query-agent.ts" +/** Storage operations used to persist and replay query-agent conversation entries. */ export interface ConversationStorage { getOrCreateConversation(): Promise<{ id: string }> appendEntry( @@ -28,11 +30,13 @@ export interface ConversationStorage { listEntries(conversationId: string): Promise } +/** Minimal persisted entry shape needed by recording and replay agents. */ export type ConversationStorageEntry = Pick< ConversationEntryRow, "id" | "sequence" | "kind" | "payload" | "metadata" | "createdAt" > +/** Configuration for wrapping a QueryAgent with conversation recording. */ export interface ConversationRecordingQueryAgentConfig { agent: QueryAgent storage: ConversationStorage diff --git a/apps/freya-backend/src/agent/pi-query-agent.test.ts b/apps/freya-backend/src/agent/pi-query-agent.test.ts index c97e132..5e6bfab 100644 --- a/apps/freya-backend/src/agent/pi-query-agent.test.ts +++ b/apps/freya-backend/src/agent/pi-query-agent.test.ts @@ -1,9 +1,9 @@ +import { ConversationEntryKind } from "@freya/core" import { beforeEach, describe, expect, mock, test } from "bun:test" import type { QueryAgentToolbox } from "./query-agent-toolbox.ts" import type { QueryAgentStreamEvent } from "./query-agent.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { QueryAgentEvent } from "./query-agent.ts" interface FakePiSession { diff --git a/apps/freya-backend/src/agent/pi-query-agent.ts b/apps/freya-backend/src/agent/pi-query-agent.ts index a08593c..8c285ca 100644 --- a/apps/freya-backend/src/agent/pi-query-agent.ts +++ b/apps/freya-backend/src/agent/pi-query-agent.ts @@ -33,13 +33,25 @@ import { import { createSessionManager } from "./session-manager.ts" import { createFreyaAgentTools, FREYA_AGENT_TOOL_NAMES } from "./tools.ts" +/** Active Pi SDK session instance returned by createAgentSession. */ type PiSession = Awaited>["session"] + +/** Pi event emitted when a message finishes. */ type PiMessageEndEvent = Extract + +/** Message payload carried by Pi's message-end event. */ type PiAgentMessage = PiMessageEndEvent["message"] + +/** Pi event emitted when an agent run finishes. */ type PiAgentEndEvent = Extract + +/** Session manager created for Pi conversation replay. */ type PiSessionManager = ReturnType + +/** Message shape accepted by the replay session manager. */ type PiSessionMessage = Parameters[0] +/** Configuration for the Pi-backed query agent. */ export interface PiQueryAgentConfig { toolbox: QueryAgentToolbox apiKey?: string diff --git a/apps/freya-backend/src/agent/session-manager.test.ts b/apps/freya-backend/src/agent/session-manager.test.ts index 925aae0..f7a6116 100644 --- a/apps/freya-backend/src/agent/session-manager.test.ts +++ b/apps/freya-backend/src/agent/session-manager.test.ts @@ -1,8 +1,8 @@ +import { ConversationEntryKind } from "@freya/core" import { describe, expect, test } from "bun:test" import type { ConversationStorageEntry } from "./conversation-recording-query-agent.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { createSessionManager } from "./session-manager.ts" describe("createSessionManager", () => { diff --git a/apps/freya-backend/src/agent/session-manager.ts b/apps/freya-backend/src/agent/session-manager.ts index 8716905..2a8f1a6 100644 --- a/apps/freya-backend/src/agent/session-manager.ts +++ b/apps/freya-backend/src/agent/session-manager.ts @@ -1,18 +1,21 @@ import { SessionManager } from "@earendil-works/pi-coding-agent" -import { tmpdir } from "node:os" - -import type { ConversationStorageEntry } from "./conversation-recording-query-agent.ts" - import { AssistantMessagePayload, ContextSummaryPayload, ConversationEntryKind, UserMessagePayload, -} from "../conversations/types.ts" +} from "@freya/core" +import { tmpdir } from "node:os" +import type { ConversationStorageEntry } from "./conversation-recording-query-agent.ts" + +/** Message shape accepted by Pi's SessionManager.appendMessage API. */ type PiMessage = Parameters[0] + +/** Assistant message variant required when replaying stored assistant entries. */ type PiAssistantMessage = Extract +/** Inputs required to rebuild a Pi session manager from stored conversation entries. */ export interface CreateSessionManagerInput { cwd?: string entries: ConversationStorageEntry[] diff --git a/apps/freya-backend/src/conversations/http.test.ts b/apps/freya-backend/src/conversations/http.test.ts index 6e51f66..fc093e8 100644 --- a/apps/freya-backend/src/conversations/http.test.ts +++ b/apps/freya-backend/src/conversations/http.test.ts @@ -1,3 +1,4 @@ +import { ConversationEntryKind, ConversationEntryVisibility } from "@freya/core" import { beforeEach, describe, expect, mock, test } from "bun:test" import { Hono } from "hono" @@ -11,7 +12,6 @@ import type { import { mockAuthSessionMiddleware } from "../auth/session-middleware.ts" import { ConversationNotFoundError } from "./errors.ts" import { registerConversationsHttpHandlers } from "./http.ts" -import { ConversationEntryKind, ConversationEntryVisibility } from "./types.ts" const MockUserId = "k7Gx2mPqRvNwYs9TdLfA4bHcJeUo1iZn" const ConversationId = "11111111-1111-4111-8111-111111111111" diff --git a/apps/freya-backend/src/conversations/http.ts b/apps/freya-backend/src/conversations/http.ts index cd534ee..5cc8d86 100644 --- a/apps/freya-backend/src/conversations/http.ts +++ b/apps/freya-backend/src/conversations/http.ts @@ -1,5 +1,6 @@ import type { Context, Hono } from "hono" +import { ConversationEntryVisibility } from "@freya/core" import { type } from "arktype" import { createMiddleware } from "hono/factory" @@ -9,20 +10,22 @@ import type { ConversationRow } from "./storage.ts" import { ConversationNotFoundError } from "./errors.ts" import { conversations } from "./storage.ts" -import { ConversationEntryVisibility } from "./types.ts" +/** Hono environment populated by the conversations route middleware. */ type Env = { Variables: { db: Database } } +/** 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 diff --git a/apps/freya-backend/src/conversations/storage.ts b/apps/freya-backend/src/conversations/storage.ts index 0480107..67b31a7 100644 --- a/apps/freya-backend/src/conversations/storage.ts +++ b/apps/freya-backend/src/conversations/storage.ts @@ -1,17 +1,18 @@ +import { + AssistantMessagePayload, + AttachmentPayload, + ConversationEntryKind, + ConversationEntryVisibility, + ContextSummaryPayload, + ConversationEntryMetadata, + GenericObjectPayload, + UserMessagePayload, + type ConversationEntryPayload, +} from "@freya/core" +import { type } from "arktype" import { and, asc, desc, eq } from "drizzle-orm" import type { Database } from "../db/index.ts" -import type { - AssistantMessagePayload, - AttachmentPayload, - ContextSummaryPayload, - ConversationEntryKind as ConversationEntryKindType, - ConversationEntryMetadata, - ConversationEntryPayload, - ConversationEntryVisibility as ConversationEntryVisibilityType, - GenericObjectPayload, - UserMessagePayload, -} from "./types.ts" import { conversationEntries, @@ -20,23 +21,20 @@ import { user, } from "../db/schema.ts" import { ConversationNotFoundError } from "./errors.ts" -import { - ConversationEntryMetadata as ConversationEntryMetadataSchema, - AssistantMessagePayload as AssistantMessagePayloadSchema, - AttachmentPayload as AttachmentPayloadSchema, - ConversationEntryKind, - ConversationEntryKindInput, - ConversationEntryVisibility, - ConversationEntryVisibilityInput, - ContextSummaryPayload as ContextSummaryPayloadSchema, - GenericObjectPayload as GenericObjectPayloadSchema, - UserMessagePayload as UserMessagePayloadSchema, -} from "./types.ts" +const conversationEntryKind = type.enumerated(...Object.values(ConversationEntryKind)) +const conversationEntryVisibility = type.enumerated(...Object.values(ConversationEntryVisibility)) + +/** Database row shape for a conversation owned by a user. */ export type ConversationRow = typeof conversationsTable.$inferSelect + +/** Database row shape for an entry in a conversation timeline. */ export type ConversationEntryRow = typeof conversationEntries.$inferSelect + +/** Database row shape for an uploaded file referenced by conversations. */ export type FileRow = typeof files.$inferSelect +/** Input required to create a stored file record. */ export interface CreateFileInput { storageKey: string originalName?: string @@ -45,23 +43,27 @@ export interface CreateFileInput { metadata?: Record } +/** Input for creating a file and appending its attachment entry together. */ export interface AppendAttachmentEntryInput { file: CreateFileInput payload: AttachmentPayload - visibility?: ConversationEntryVisibilityType + visibility?: ConversationEntryVisibility metadata?: ConversationEntryMetadata } +/** Result returned after a file-backed attachment entry is appended. */ export interface AppendAttachmentEntryResult { file: FileRow entry: ConversationEntryRow } +/** Common fields accepted when appending any conversation entry. */ interface AppendConversationEntryBase { - visibility?: ConversationEntryVisibilityType + visibility?: ConversationEntryVisibility metadata?: ConversationEntryMetadata } +/** Discriminated input for appending any supported entry kind to a conversation. */ export type AppendConversationEntryInput = | (AppendConversationEntryBase & { kind: typeof ConversationEntryKind.UserMessage @@ -92,8 +94,9 @@ export type AppendConversationEntryInput = fileId?: never }) +/** Filters accepted when listing conversation entries. */ export interface ListConversationEntriesParams { - visibility?: ConversationEntryVisibilityType + visibility?: ConversationEntryVisibility } export function conversations(db: Database, userId: string) { @@ -140,12 +143,12 @@ export function conversations(db: Database, userId: string) { conversationId: string, input: AppendConversationEntryInput, ): Promise { - const kind = ConversationEntryKindInput.assert(input.kind) - const visibility = ConversationEntryVisibilityInput.assert( + const kind = conversationEntryKind.assert(input.kind) + const visibility = conversationEntryVisibility.assert( input.visibility ?? defaultVisibilityForKind(kind), ) const payload = payloadForKind(kind, input.payload) - const metadata = ConversationEntryMetadataSchema.assert(input.metadata ?? {}) + const metadata = ConversationEntryMetadata.assert(input.metadata ?? {}) let fileId: string | null = null if (input.kind === ConversationEntryKind.Attachment) { @@ -183,11 +186,11 @@ export function conversations(db: Database, userId: string) { conversationId: string, input: AppendAttachmentEntryInput, ): Promise { - const payload = AttachmentPayloadSchema.assert(input.payload) - const visibility = ConversationEntryVisibilityInput.assert( + const payload = AttachmentPayload.assert(input.payload) + const visibility = conversationEntryVisibility.assert( input.visibility ?? defaultVisibilityForKind(ConversationEntryKind.Attachment), ) - const metadata = ConversationEntryMetadataSchema.assert(input.metadata ?? {}) + const metadata = ConversationEntryMetadata.assert(input.metadata ?? {}) return db.transaction(async (tx) => { if (!(await findConversationForUpdate(tx, userId, conversationId))) { @@ -250,22 +253,22 @@ export function conversations(db: Database, userId: string) { } function payloadForKind( - kind: ConversationEntryKindType, + kind: ConversationEntryKind, payload: AppendConversationEntryInput["payload"], ): ConversationEntryPayload { switch (kind) { case ConversationEntryKind.UserMessage: - return UserMessagePayloadSchema.assert(payload) + return UserMessagePayload.assert(payload) case ConversationEntryKind.AssistantMessage: - return AssistantMessagePayloadSchema.assert(payload) + return AssistantMessagePayload.assert(payload) case ConversationEntryKind.Attachment: - return AttachmentPayloadSchema.assert(payload) + return AttachmentPayload.assert(payload) case ConversationEntryKind.ContextSummary: - return ContextSummaryPayloadSchema.assert(payload) + return ContextSummaryPayload.assert(payload) case ConversationEntryKind.ToolCall: case ConversationEntryKind.ToolResult: case ConversationEntryKind.SystemNote: - return GenericObjectPayloadSchema.assert(payload) + return GenericObjectPayload.assert(payload) } } @@ -371,9 +374,7 @@ function requireRow(rows: T[], message = "Expected database row"): T { return row } -function defaultVisibilityForKind( - kind: ConversationEntryKindType, -): ConversationEntryVisibilityType { +function defaultVisibilityForKind(kind: ConversationEntryKind): ConversationEntryVisibility { switch (kind) { case ConversationEntryKind.UserMessage: case ConversationEntryKind.AssistantMessage: diff --git a/apps/freya-backend/src/db/schema.ts b/apps/freya-backend/src/db/schema.ts index b4f3258..d822c90 100644 --- a/apps/freya-backend/src/db/schema.ts +++ b/apps/freya-backend/src/db/schema.ts @@ -1,3 +1,10 @@ +import { + ConversationEntryVisibility, + type ConversationEntryKind, + type ConversationEntryMetadata, + type ConversationEntryPayload, + type ConversationEntryVisibility as ConversationEntryVisibilityType, +} from "@freya/core" import { sql } from "drizzle-orm" import { boolean, @@ -13,14 +20,6 @@ import { uuid, } from "drizzle-orm/pg-core" -import { - ConversationEntryVisibility, - type ConversationEntryKind, - type ConversationEntryMetadata, - type ConversationEntryPayload, - type ConversationEntryVisibility as ConversationEntryVisibilityType, -} from "../conversations/types.ts" - // --------------------------------------------------------------------------- // Better Auth core tables // Re-exported from CLI-generated schema. diff --git a/apps/freya-backend/src/session/user-session-manager.test.ts b/apps/freya-backend/src/session/user-session-manager.test.ts index 852a32c..48f5764 100644 --- a/apps/freya-backend/src/session/user-session-manager.test.ts +++ b/apps/freya-backend/src/session/user-session-manager.test.ts @@ -1,5 +1,6 @@ import type { ActionDefinition, ContextEntry, FeedItem, FeedSource } from "@freya/core" +import { ConversationEntryKind } from "@freya/core" import { LocationSource } from "@freya/source-location" import { WeatherSource } from "@freya/source-weatherkit" import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test" @@ -9,7 +10,6 @@ import type { AppendConversationEntryInput } from "../conversations/storage.ts" import type { Database } from "../db/index.ts" import type { FeedSourceProvider } from "./feed-source-provider.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { CredentialEncryptor } from "../lib/crypto.ts" import { CredentialStorageUnavailableError, diff --git a/apps/freya-backend/src/session/user-session.test.ts b/apps/freya-backend/src/session/user-session.test.ts index 0933549..20dd051 100644 --- a/apps/freya-backend/src/session/user-session.test.ts +++ b/apps/freya-backend/src/session/user-session.test.ts @@ -1,5 +1,6 @@ import type { ActionDefinition, ContextEntry, FeedItem, FeedSource } from "@freya/core" +import { ConversationEntryKind } from "@freya/core" import { LocationSource } from "@freya/source-location" import { describe, expect, spyOn, test } from "bun:test" @@ -9,7 +10,6 @@ import type { } from "../agent/conversation-recording-query-agent.ts" import type { AppendConversationEntryInput } from "../conversations/storage.ts" -import { ConversationEntryKind } from "../conversations/types.ts" import { UserSession } from "./user-session.ts" function createStubSource(id: string, items: FeedItem[] = []): FeedSource { diff --git a/bun.lock b/bun.lock index 9ad947a..0f31646 100644 --- a/bun.lock +++ b/bun.lock @@ -172,6 +172,7 @@ "version": "0.0.0", "dependencies": { "@standard-schema/spec": "^1.1.0", + "arktype": "^2.1.29", }, "peerDependencies": { "@json-render/core": "*", diff --git a/packages/freya-core/package.json b/packages/freya-core/package.json index b2a9775..2668d58 100644 --- a/packages/freya-core/package.json +++ b/packages/freya-core/package.json @@ -8,7 +8,8 @@ "test": "bun test ." }, "dependencies": { - "@standard-schema/spec": "^1.1.0" + "@standard-schema/spec": "^1.1.0", + "arktype": "^2.1.29" }, "peerDependencies": { "@json-render/core": "*", diff --git a/apps/freya-backend/src/conversations/types.test.ts b/packages/freya-core/src/conversation.test.ts similarity index 99% rename from apps/freya-backend/src/conversations/types.test.ts rename to packages/freya-core/src/conversation.test.ts index 9c513fe..1297e05 100644 --- a/apps/freya-backend/src/conversations/types.test.ts +++ b/packages/freya-core/src/conversation.test.ts @@ -7,7 +7,7 @@ import { ConversationEntryMetadata, GenericObjectPayload, UserMessagePayload, -} from "./types.ts" +} from "./conversation" describe("conversation entry schemas", () => { test("parses valid user message payloads", () => { diff --git a/apps/freya-backend/src/conversations/types.ts b/packages/freya-core/src/conversation.ts similarity index 61% rename from apps/freya-backend/src/conversations/types.ts rename to packages/freya-core/src/conversation.ts index e178770..276e34a 100644 --- a/apps/freya-backend/src/conversations/types.ts +++ b/packages/freya-core/src/conversation.ts @@ -1,5 +1,6 @@ import { type } from "arktype" +/** Entry kinds supported by the persisted conversation timeline. */ export const ConversationEntryKind = { UserMessage: "user_message", AssistantMessage: "assistant_message", @@ -10,17 +11,21 @@ export const ConversationEntryKind = { SystemNote: "system_note", } as const +/** Discriminator for the payload shape and handling of a conversation entry. */ export type ConversationEntryKind = (typeof ConversationEntryKind)[keyof typeof ConversationEntryKind] +/** Visibility scopes supported by stored conversation entries. */ export const ConversationEntryVisibility = { UserVisible: "user_visible", Internal: "internal", } as const +/** Indicates whether a conversation entry should be exposed to the user. */ export type ConversationEntryVisibility = (typeof ConversationEntryVisibility)[keyof typeof ConversationEntryVisibility] +/** Attachment media categories accepted by conversation entries. */ export const AttachmentType = { Image: "image", Audio: "audio", @@ -29,57 +34,64 @@ export const AttachmentType = { Other: "other", } as const +/** File or media category associated with an attachment payload. */ export type AttachmentType = (typeof AttachmentType)[keyof typeof AttachmentType] -export const ConversationEntryKindInput = type.enumerated(...Object.values(ConversationEntryKind)) -export const ConversationEntryVisibilityInput = type.enumerated( - ...Object.values(ConversationEntryVisibility), -) -export const AttachmentTypeInput = type.enumerated(...Object.values(AttachmentType)) - -const TextMessagePart = type({ +/** Plain text content part for a message. */ +export const TextMessagePart = type({ "+": "reject", type: "'text'", text: "string", }) -const JsonMessagePart = type({ +/** Structured JSON content part for a message. */ +export const JsonMessagePart = type({ "+": "reject", type: "'json'", value: "unknown", }) +/** Content part variants supported by user and assistant messages. */ export const MessagePart = type.or(TextMessagePart, JsonMessagePart) + +/** A structured content part inside a user or assistant message payload. */ export type MessagePart = typeof MessagePart.infer +/** User-authored message entry payload. */ export const UserMessagePayload = type({ "+": "reject", role: "'user'", parts: MessagePart.array().atLeastLength(1), }) +/** Payload stored for a conversation entry containing a user message. */ export type UserMessagePayload = typeof UserMessagePayload.infer +/** Assistant-authored message entry payload. */ export const AssistantMessagePayload = type({ "+": "reject", role: "'assistant'", parts: MessagePart.array().atLeastLength(1), }) +/** Payload stored for a conversation entry containing an assistant message. */ export type AssistantMessagePayload = typeof AssistantMessagePayload.infer +/** Attachment entry payload. */ export const AttachmentPayload = type({ "+": "reject", role: type.enumerated("user", "assistant"), name: "string", mimeType: "string", - attachmentType: AttachmentTypeInput, + attachmentType: type.enumerated(...Object.values(AttachmentType)), "caption?": "string", }) +/** Payload stored for a conversation entry that references an uploaded file. */ export type AttachmentPayload = typeof AttachmentPayload.infer -const ContextSummary = type({ +/** Durable facts extracted from compacted conversation history. */ +export const ContextSummary = type({ "+": "reject", "userIntent?": "string", durableFacts: type.string.array(), @@ -89,6 +101,10 @@ const ContextSummary = type({ importantDetails: type.string.array(), }) +/** Durable facts and follow-ups retained from compacted conversation history. */ +export type ContextSummary = typeof ContextSummary.infer + +/** Context-summary conversation entry payload. */ export const ContextSummaryPayload = type({ "+": "reject", covers: type({ @@ -101,8 +117,10 @@ export const ContextSummaryPayload = type({ "sourceEntryIds?": type.string.array(), }) +/** Payload describing a compaction summary and the sequence range it covers. */ export type ContextSummaryPayload = typeof ContextSummaryPayload.infer +/** Model invocation metadata recorded on generated entries. */ export const ModelRunMetadata = type({ "+": "reject", route: "string", @@ -116,18 +134,25 @@ export const ModelRunMetadata = type({ "providerRequestId?": "string", }) +/** Metadata describing the model run that produced a conversation entry. */ export type ModelRunMetadata = typeof ModelRunMetadata.infer +/** Arbitrary metadata stored alongside conversation entries. */ export const ConversationEntryMetadata = type({ "modelRun?": ModelRunMetadata, "[string]": "unknown", }) +/** Metadata bag attached to a conversation entry. */ export type ConversationEntryMetadata = typeof ConversationEntryMetadata.infer +/** Generic object payload used by operational entries. */ export const GenericObjectPayload = type("Record") + +/** Fallback payload shape for tool calls, tool results, and system notes. */ export type GenericObjectPayload = typeof GenericObjectPayload.infer +/** Union of payload shapes that can be stored on a conversation entry. */ export type ConversationEntryPayload = | UserMessagePayload | AssistantMessagePayload diff --git a/packages/freya-core/src/index.ts b/packages/freya-core/src/index.ts index 5ed8ba2..97a64cf 100644 --- a/packages/freya-core/src/index.ts +++ b/packages/freya-core/src/index.ts @@ -6,6 +6,25 @@ export { Context, contextKey, serializeKey } from "./context" export type { ActionDefinition } from "./action" export { UnknownActionError } from "./action" +// Conversation +export type { ConversationEntryPayload } from "./conversation" +export { + AssistantMessagePayload, + AttachmentPayload, + AttachmentType, + ContextSummary, + ContextSummaryPayload, + ConversationEntryKind, + ConversationEntryMetadata, + ConversationEntryVisibility, + GenericObjectPayload, + JsonMessagePart, + MessagePart, + ModelRunMetadata, + TextMessagePart, + UserMessagePayload, +} from "./conversation" + // Feed export type { FeedItem, FeedItemRenderer, FeedItemSignals, RenderedFeedItem, Slot } from "./feed" export { TimeRelevance } from "./feed"