refactor(core): make v2 session inputs event sourced (#30785)
This commit is contained in:
parent
057958c933
commit
76ecf2e58c
43 changed files with 4671 additions and 757 deletions
|
|
@ -19,7 +19,7 @@ type DatabaseService = Database.Interface["db"]
|
|||
const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message)
|
||||
const encodeMessage = Schema.encodeSync(SessionMessage.Message)
|
||||
|
||||
export class PromptAlreadyProjected extends Error {}
|
||||
class PromptAlreadyProjected extends Error {}
|
||||
export class SessionAlreadyProjected extends Error {}
|
||||
|
||||
type Usage = {
|
||||
|
|
@ -112,29 +112,23 @@ function run(db: DatabaseService, event: SessionEvent.Event) {
|
|||
return Effect.gen(function* () {
|
||||
const decodeRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
const writeMessage = (message: SessionMessage.Message) => {
|
||||
const updateMessage = (message: SessionMessage.Message) => {
|
||||
if (event.seq === undefined) return Effect.die("Synchronized Session event is missing aggregate sequence")
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
return db
|
||||
.insert(SessionMessageTable)
|
||||
.values([
|
||||
{
|
||||
id: SessionMessage.ID.make(id),
|
||||
session_id: event.data.sessionID,
|
||||
type,
|
||||
seq: event.seq,
|
||||
time_created: DateTime.toEpochMillis(message.time.created),
|
||||
data,
|
||||
},
|
||||
])
|
||||
.onConflictDoUpdate({
|
||||
target: SessionMessageTable.id,
|
||||
set: { type, time_created: DateTime.toEpochMillis(message.time.created), data },
|
||||
})
|
||||
.update(SessionMessageTable)
|
||||
.set({ type, time_created: DateTime.toEpochMillis(message.time.created), data })
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.id, SessionMessage.ID.make(id)),
|
||||
eq(SessionMessageTable.session_id, event.data.sessionID),
|
||||
),
|
||||
)
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
const appendMessage = (message: SessionMessage.Message) => insertMessage(db, event, message)
|
||||
const adapter: SessionMessageUpdater.Adapter = {
|
||||
getCurrentAssistant() {
|
||||
return Effect.gen(function* () {
|
||||
|
|
@ -204,15 +198,33 @@ function run(db: DatabaseService, event: SessionEvent.Event) {
|
|||
.find((message): message is SessionMessage.Shell => message.type === "shell" && message.callID === callID)
|
||||
})
|
||||
},
|
||||
updateAssistant: writeMessage,
|
||||
updateCompaction: writeMessage,
|
||||
updateShell: writeMessage,
|
||||
appendMessage: writeMessage,
|
||||
updateAssistant: updateMessage,
|
||||
updateCompaction: updateMessage,
|
||||
updateShell: updateMessage,
|
||||
appendMessage,
|
||||
}
|
||||
yield* SessionMessageUpdater.update(adapter, event)
|
||||
})
|
||||
}
|
||||
|
||||
function insertMessage(db: DatabaseService, event: SessionEvent.Event, message: SessionMessage.Message) {
|
||||
if (event.seq === undefined) return Effect.die("Synchronized Session event is missing aggregate sequence")
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
return db
|
||||
.insert(SessionMessageTable)
|
||||
.values({
|
||||
id: SessionMessage.ID.make(id),
|
||||
session_id: event.data.sessionID,
|
||||
type,
|
||||
seq: event.seq,
|
||||
time_created: DateTime.toEpochMillis(message.time.created),
|
||||
data,
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
export const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
|
@ -349,27 +361,19 @@ export const layer = Layer.effectDiscard(
|
|||
)
|
||||
yield* events.project(SessionEvent.Prompted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
const messageID = event.data.messageID
|
||||
const existing = yield* db
|
||||
.select({ id: SessionMessageTable.id })
|
||||
.from(SessionMessageTable)
|
||||
.where(eq(SessionMessageTable.id, event.id))
|
||||
.where(eq(SessionMessageTable.id, messageID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (existing) return yield* Effect.die(new PromptAlreadyProjected())
|
||||
yield* run(db, event)
|
||||
const row = yield* db
|
||||
.select()
|
||||
.from(SessionMessageTable)
|
||||
.where(eq(SessionMessageTable.id, event.id))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row) return yield* Effect.die("Prompt projection was not stored")
|
||||
const message = decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
if (message.type !== "user") return yield* Effect.die("Prompt projection did not produce a user message")
|
||||
if (event.seq === undefined)
|
||||
return yield* Effect.die("Synchronized Session event is missing aggregate sequence")
|
||||
yield* SessionInput.project(db, {
|
||||
id: SessionMessage.ID.make(event.id),
|
||||
yield* SessionInput.projectLegacyPrompted(db, {
|
||||
id: messageID,
|
||||
sessionID: event.data.sessionID,
|
||||
prompt: event.data.prompt,
|
||||
delivery: event.data.delivery,
|
||||
|
|
@ -378,6 +382,37 @@ export const layer = Layer.effectDiscard(
|
|||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.PromptLifecycle.Admitted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.seq === undefined)
|
||||
return yield* Effect.die("Synchronized Session event is missing aggregate sequence")
|
||||
yield* SessionInput.projectAdmitted(db, {
|
||||
admittedSeq: event.seq,
|
||||
id: event.data.messageID,
|
||||
sessionID: event.data.sessionID,
|
||||
prompt: event.data.prompt,
|
||||
delivery: event.data.delivery,
|
||||
timeCreated: event.data.timestamp,
|
||||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.PromptLifecycle.Promoted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.seq === undefined)
|
||||
return yield* Effect.die("Synchronized Session event is missing aggregate sequence")
|
||||
yield* insertMessage(
|
||||
db,
|
||||
event,
|
||||
yield* SessionInput.projectPromoted(db, {
|
||||
id: event.data.messageID,
|
||||
sessionID: event.data.sessionID,
|
||||
prompt: event.data.prompt,
|
||||
timeCreated: event.data.timeCreated,
|
||||
promotedSeq: event.seq,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Synthetic, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Shell.Started, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Shell.Ended, (event) => run(db, event))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue