Refactor v2 session events as schemas (#24512)

This commit is contained in:
Dax
2026-05-02 22:09:48 -04:00
committed by GitHub
parent 1409a0715c
commit a3bc5d35b0
62 changed files with 12801 additions and 6015 deletions
+116 -3
View File
@@ -54,6 +54,13 @@ import { InstanceState } from "@/effect/instance-state"
import { TaskTool, type TaskPromptOps } from "@/tool/task"
import { SessionRunState } from "./run-state"
import { EffectBridge } from "@/effect/bridge"
import { EventV2 } from "@/v2/event"
import { SessionEvent } from "@/v2/session-event"
import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt"
import * as DateTime from "effect/DateTime"
import { eq } from "@/storage/db"
import * as Database from "@/storage/db"
import { SessionTable } from "./session.sql"
// @ts-ignore
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -785,6 +792,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
providerID: model.providerID,
}
yield* sessions.updateMessage(msg)
const callID = ulid()
const started = Date.now()
const part: MessageV2.ToolPart = {
type: "tool",
id: PartID.ascending(),
@@ -794,11 +803,17 @@ NOTE: At any point in time through this workflow you should feel free to ask the
callID: ulid(),
state: {
status: "running",
time: { start: Date.now() },
time: { start: started },
input: { command: input.command },
},
}
yield* sessions.updatePart(part)
EventV2.run(SessionEvent.Shell.Started.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(started),
callID,
command: input.command,
})
return { msg, part, cwd: ctx.directory }
}).pipe(Effect.ensuring(markReady))
@@ -813,14 +828,21 @@ NOTE: At any point in time through this workflow you should feel free to ask the
if (aborted) {
output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
}
const completed = Date.now()
EventV2.run(SessionEvent.Shell.Ended.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(completed),
callID: part.callID,
output,
})
if (!msg.time.completed) {
msg.time.completed = Date.now()
msg.time.completed = completed
yield* sessions.updateMessage(msg)
}
if (part.state.status === "running") {
part.state = {
status: "completed",
time: { ...part.state.time, end: Date.now() },
time: { ...part.state.time, end: completed },
input: part.state.input,
title: "",
metadata: { output, description: "" },
@@ -934,6 +956,34 @@ NOTE: At any point in time through this workflow you should feel free to ask the
format: input.format,
}
const current = Database.use((db) =>
db
.select({ agent: SessionTable.agent, model: SessionTable.model })
.from(SessionTable)
.where(eq(SessionTable.id, input.sessionID))
.get(),
)
if (current?.agent !== info.agent) {
EventV2.run(SessionEvent.AgentSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
agent: info.agent,
})
}
if (
current?.model?.providerID !== info.model.providerID ||
current.model.id !== info.model.modelID ||
current.model.variant !== info.model.variant
) {
EventV2.run(SessionEvent.ModelSwitched.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
id: info.model.modelID,
providerID: info.model.providerID,
variant: info.model.variant,
})
}
yield* Effect.addFinalizer(() => instruction.clear(info.id))
type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
@@ -1250,6 +1300,69 @@ NOTE: At any point in time through this workflow you should feel free to ask the
yield* sessions.updateMessage(info)
for (const part of parts) yield* sessions.updatePart(part)
const nextPrompt = parts.reduce(
(result, part) => {
if (part.type === "text") {
if (part.synthetic) result.synthetic.push(part.text)
else result.text.push(part.text)
}
if (part.type === "file") {
result.files.push(
new FileAttachment({
uri: part.url,
mime: part.mime,
name: part.filename,
source: part.source
? new Source({
start: part.source.text.start,
end: part.source.text.end,
text: part.source.text.value,
})
: undefined,
}),
)
}
if (part.type === "agent") {
result.agents.push(
new AgentAttachment({
name: part.name,
source: part.source
? new Source({
start: part.source.start,
end: part.source.end,
text: part.source.value,
})
: undefined,
}),
)
}
return result
},
{
text: [] as string[],
files: [] as FileAttachment[],
agents: [] as AgentAttachment[],
synthetic: [] as string[],
},
)
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Prompted.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
prompt: {
text: nextPrompt.text.join("\n"),
files: nextPrompt.files,
agents: nextPrompt.agents,
},
})
for (const text of nextPrompt.synthetic) {
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
EventV2.run(SessionEvent.Synthetic.Sync, {
sessionID: input.sessionID,
timestamp: DateTime.makeUnsafe(info.time.created),
text,
})
}
return { info, parts }
}, Effect.scoped)