feat(core): generalize session input inbox (#36005)

This commit is contained in:
Kit Langton 2026-07-08 22:07:45 -04:00 committed by GitHub
commit 984cab7938
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1590 additions and 767 deletions

View file

@ -5,10 +5,9 @@ import { optional } from "./schema.js"
import { Event } from "./event.js"
import { ToolContent } from "./llm.js"
import { FinishReason } from "./llm.js"
import { Delivery } from "./session-delivery.js"
import { Model } from "./model.js"
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema.js"
import { FileAttachment, Prompt } from "./prompt.js"
import { FileAttachment } from "./prompt.js"
import { SessionID } from "./session-id.js"
import { Location } from "./location.js"
import { SessionMessage } from "./session-message.js"
@ -20,6 +19,7 @@ import { Skill as SkillSchema } from "./skill.js"
import { Money } from "./money.js"
import { Snapshot } from "./snapshot.js"
import { TokenUsage } from "./token-usage.js"
import { SessionInput } from "./session-input.js"
export { FileAttachment }
@ -35,12 +35,6 @@ export interface Source extends Schema.Schema.Type<typeof Source> {}
const Base = {
sessionID: SessionID,
}
const PromptFields = {
...Base,
inputID: SessionMessage.ID,
prompt: Prompt,
delivery: Delivery,
}
const options = {
durable: {
@ -120,22 +114,26 @@ export const Forked = Event.durable({
})
export type Forked = typeof Forked.Type
export const PromptPromoted = Event.durable({
type: "session.prompt.promoted",
export const InputPromoted = Event.durable({
type: "session.input.promoted",
...options,
schema: {
sessionID: SessionID,
inputID: SessionMessage.ID,
},
})
export type PromptPromoted = typeof PromptPromoted.Type
export type InputPromoted = typeof InputPromoted.Type
export const PromptAdmitted = Event.durable({
type: "session.prompt.admitted",
export const InputAdmitted = Event.durable({
type: "session.input.admitted",
...options,
schema: PromptFields,
schema: {
...Base,
inputID: SessionMessage.ID,
input: SessionInput.Message,
},
})
export type PromptAdmitted = typeof PromptAdmitted.Type
export type InputAdmitted = typeof InputAdmitted.Type
export namespace Execution {
export const Started = Event.durable({ type: "session.execution.started", ...options, schema: Base })
@ -523,8 +521,8 @@ export const Definitions = Event.inventory(
UsageUpdated,
Deleted,
Forked,
PromptPromoted,
PromptAdmitted,
InputPromoted,
InputAdmitted,
Execution.Started,
Execution.Succeeded,
Execution.Failed,

View file

@ -11,34 +11,70 @@ import { SessionMessage } from "./session-message.js"
export const Delivery = SessionDelivery.Delivery
export type Delivery = SessionDelivery.Delivery
export interface Admitted extends Schema.Schema.Type<typeof Admitted> {}
export const Admitted = Schema.Struct({
export interface UserData extends Schema.Schema.Type<typeof UserData> {}
export const UserData = Schema.Struct({
...Prompt.fields,
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
}).annotate({ identifier: "SessionInput.UserData" })
export interface SyntheticData extends Schema.Schema.Type<typeof SyntheticData> {}
export const SyntheticData = Schema.Struct({
text: Schema.String,
description: Schema.String.pipe(optional),
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
}).annotate({ identifier: "SessionInput.SyntheticData" })
export interface UserMessage extends Schema.Schema.Type<typeof UserMessage> {}
export const UserMessage = Schema.Struct({
type: Schema.tag("user"),
data: UserData,
delivery: Delivery,
}).annotate({ identifier: "SessionInput.UserMessage" })
export interface SyntheticMessage extends Schema.Schema.Type<typeof SyntheticMessage> {}
export const SyntheticMessage = Schema.Struct({
type: Schema.tag("synthetic"),
data: SyntheticData,
delivery: Delivery,
}).annotate({ identifier: "SessionInput.SyntheticMessage" })
export const Message = Schema.Union([UserMessage, SyntheticMessage]).pipe(
Schema.toTaggedUnion("type"),
Schema.annotate({ identifier: "SessionInput.Message" }),
)
export type Message = typeof Message.Type
const Admitted = {
admittedSeq: NonNegativeInt,
id: SessionMessage.ID,
sessionID: SessionID,
prompt: Prompt,
delivery: Delivery,
timeCreated: DateTimeUtcFromMillis,
}
const MessageLifecycle = {
...Admitted,
promotedSeq: NonNegativeInt.pipe(optional),
}).annotate({ identifier: "SessionInput.Admitted" })
}
export interface PromptEntry extends Schema.Schema.Type<typeof PromptEntry> {}
export const PromptEntry = Schema.Struct({
type: Schema.tag("prompt"),
...Admitted.fields,
}).annotate({ identifier: "SessionInput.PromptEntry" })
export interface User extends Schema.Schema.Type<typeof User> {}
export const User = Schema.Struct({
...MessageLifecycle,
...UserMessage.fields,
}).annotate({ identifier: "SessionInput.User" })
export interface Synthetic extends Schema.Schema.Type<typeof Synthetic> {}
export const Synthetic = Schema.Struct({
...MessageLifecycle,
...SyntheticMessage.fields,
}).annotate({ identifier: "SessionInput.Synthetic" })
export interface Compaction extends Schema.Schema.Type<typeof Compaction> {}
export const Compaction = Schema.Struct({
...Admitted,
type: Schema.tag("compaction"),
admittedSeq: NonNegativeInt,
id: SessionMessage.ID,
sessionID: SessionID,
timeCreated: DateTimeUtcFromMillis,
handledSeq: NonNegativeInt.pipe(optional),
}).annotate({ identifier: "SessionInput.Compaction" })
export const Info = Schema.Union([PromptEntry, Compaction]).pipe(
export const Info = Schema.Union([User, Synthetic, Compaction]).pipe(
Schema.toTaggedUnion("type"),
Schema.annotate({ identifier: "SessionInput.Info" }),
)

View file

@ -39,6 +39,13 @@ describe("contract hygiene", () => {
expect(Schema.decodeUnknownSync(Value)({ value: "1" })).toEqual({ value: 1 })
expect(Schema.encodeSync(Value)({ value: 1 })).toEqual({ value: "1" })
expect(Schema.encodeSync(Value)({ value: undefined })).toEqual({})
expect(
Schema.encodeSync(SessionInput.SyntheticData)({
text: "completed",
description: undefined,
metadata: undefined,
}),
).toEqual({ text: "completed" })
})
test("model defaults and provider overlays preserve public invariants", () => {
@ -92,6 +99,10 @@ describe("contract hygiene", () => {
Pty.Info,
Session.ListAnchor,
Session.Revert,
SessionInput.UserData,
SessionInput.SyntheticData,
SessionInput.User,
SessionInput.Synthetic,
].map((schema) => schema.ast.annotations?.identifier)
expect(identifiers.every((identifier) => typeof identifier === "string")).toBe(true)

View file

@ -107,8 +107,8 @@ describe("public event manifest", () => {
"session.moved.1",
"session.renamed.1",
"session.forked.1",
"session.prompt.promoted.1",
"session.prompt.admitted.1",
"session.input.promoted.1",
"session.input.admitted.1",
"session.execution.started.1",
"session.execution.succeeded.1",
"session.execution.failed.1",