From caf48484bf5926e690ba813e0eb2eac94c9a5d44 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sun, 1 Mar 2026 23:57:51 +0000 Subject: [PATCH] feat(core): add Slot type and slots field to FeedItem (#53) Co-authored-by: Ona --- packages/aris-core/src/feed.test.ts | 87 +++++++++++++++++++++++++++++ packages/aris-core/src/feed.ts | 22 ++++++++ packages/aris-core/src/index.ts | 2 +- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 packages/aris-core/src/feed.test.ts diff --git a/packages/aris-core/src/feed.test.ts b/packages/aris-core/src/feed.test.ts new file mode 100644 index 0000000..cd6977c --- /dev/null +++ b/packages/aris-core/src/feed.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, test } from "bun:test" + +import type { FeedItem, Slot } from "./feed" + +describe("FeedItem slots", () => { + test("FeedItem without slots is valid", () => { + const item: FeedItem<"test", { value: number }> = { + id: "test-1", + type: "test", + timestamp: new Date(), + data: { value: 42 }, + } + + expect(item.slots).toBeUndefined() + }) + + test("FeedItem with unfilled slots", () => { + const item: FeedItem<"weather", { temp: number }> = { + id: "weather-1", + type: "weather", + timestamp: new Date(), + data: { temp: 18 }, + slots: { + insight: { + description: "A short contextual insight about the current weather", + content: null, + }, + "cross-source": { + description: "Connection between weather and calendar events", + content: null, + }, + }, + } + + expect(item.slots).toBeDefined() + expect(Object.keys(item.slots!)).toEqual(["insight", "cross-source"]) + expect(item.slots!.insight!.content).toBeNull() + expect(item.slots!["cross-source"]!.content).toBeNull() + }) + + test("FeedItem with filled slots", () => { + const item: FeedItem<"weather", { temp: number }> = { + id: "weather-1", + type: "weather", + timestamp: new Date(), + data: { temp: 18 }, + slots: { + insight: { + description: "A short contextual insight about the current weather", + content: "Rain after 3pm — grab a jacket before your walk", + }, + }, + } + + expect(item.slots!.insight!.content).toBe("Rain after 3pm — grab a jacket before your walk") + }) + + test("Slot interface enforces required fields", () => { + const slot: Slot = { + description: "Test slot description", + content: null, + } + + expect(slot.description).toBe("Test slot description") + expect(slot.content).toBeNull() + + const filledSlot: Slot = { + description: "Test slot description", + content: "Filled content", + } + + expect(filledSlot.content).toBe("Filled content") + }) + + test("FeedItem with empty slots record", () => { + const item: FeedItem<"test", { value: number }> = { + id: "test-1", + type: "test", + timestamp: new Date(), + data: { value: 1 }, + slots: {}, + } + + expect(item.slots).toEqual({}) + expect(Object.keys(item.slots!)).toHaveLength(0) + }) +}) diff --git a/packages/aris-core/src/feed.ts b/packages/aris-core/src/feed.ts index 9291a9e..7e1aa72 100644 --- a/packages/aris-core/src/feed.ts +++ b/packages/aris-core/src/feed.ts @@ -23,6 +23,20 @@ export interface FeedItemSignals { timeRelevance?: TimeRelevance } +/** + * A named slot for LLM-fillable content on a feed item. + * + * Sources declare slots with a description that tells the LLM what content + * to generate. The enhancement harness fills `content` asynchronously; + * until then it remains `null`. + */ +export interface Slot { + /** Tells the LLM what this slot wants — written by the source */ + description: string + /** LLM-filled text content, null until enhanced */ + content: string | null +} + /** * A single item in the feed. * @@ -36,6 +50,12 @@ export interface FeedItemSignals { * timestamp: new Date(), * data: { temp: 18, condition: "cloudy" }, * signals: { urgency: 0.5, timeRelevance: "ambient" }, + * slots: { + * insight: { + * description: "A short contextual insight about the current weather", + * content: null, + * }, + * }, * } * ``` */ @@ -53,4 +73,6 @@ export interface FeedItem< data: TData /** Source-provided hints for post-processors. Optional — omit if no signals apply. */ signals?: FeedItemSignals + /** Named slots for LLM-fillable content. Keys are slot names. */ + slots?: Record } diff --git a/packages/aris-core/src/index.ts b/packages/aris-core/src/index.ts index f9e4158..ff347fa 100644 --- a/packages/aris-core/src/index.ts +++ b/packages/aris-core/src/index.ts @@ -7,7 +7,7 @@ export type { ActionDefinition } from "./action" export { UnknownActionError } from "./action" // Feed -export type { FeedItem, FeedItemSignals } from "./feed" +export type { FeedItem, FeedItemSignals, Slot } from "./feed" export { TimeRelevance } from "./feed" // Feed Source