refactor(schema): rename V2 session events and normalize payloads (#35217)

This commit is contained in:
Kit Langton
2026-07-03 14:25:59 -04:00
committed by GitHub
parent 4790a2772c
commit 394e0b9045
63 changed files with 1939 additions and 2040 deletions
@@ -18,31 +18,33 @@ function form(id: string, sessionID: string): FormInfo {
}
function formCreated(info: FormInfo): V2Event {
return { id: `evt_${info.id}`, type: "form.created", data: { form: info } }
return { id: `evt_${info.id}`, created: 0, type: "form.created", data: { form: info } }
}
function prompted(messageID: string): V2Event {
function prompted(inputID: string): V2Event {
return {
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: { aggregateID: "ses_1", seq: 0, version: 1 },
data: { timestamp: 1, sessionID: "ses_1", messageID, prompt: { text: "hello" }, delivery: "steer" },
data: { sessionID: "ses_1", inputID },
}
}
function settled(outcome: "success" | "interrupted" = "success"): V2Event {
return {
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 2, sessionID: "ses_1", outcome },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome },
}
}
// Runs one non-interactive prompt against a mocked SDK. `turn` produces the
// live events the prompt admission triggers, keyed by the generated message ID.
async function run(input: { turn: (messageID: string) => V2Event[]; pendingForms?: FormInfo[]; attached?: boolean }) {
async function run(input: { turn: (inputID: string) => V2Event[]; pendingForms?: FormInfo[]; attached?: boolean }) {
const sdk = new OpencodeClient()
const values: V2Event[] = [{ id: "evt_connected", type: "server.connected", data: {} }]
const values: V2Event[] = [{ id: "evt_connected", created: 0, type: "server.connected", data: {} }]
let wake: (() => void) | undefined
const stream = (async function* (): AsyncGenerator<V2Event, void, unknown> {
while (true) {
@@ -329,7 +329,7 @@ describe("run session data", () => {
test("renders direct shell mode from first-class shell events", () => {
let data = createSessionData()
const started = reduce(data, {
type: "session.next.shell.started",
type: "shell.started",
properties: {
sessionID: "session-1",
timestamp: 1,
@@ -353,7 +353,7 @@ describe("run session data", () => {
data = started.data
const ended = reduce(data, {
type: "session.next.shell.ended",
type: "shell.ended",
properties: {
sessionID: "session-1",
timestamp: 2,
@@ -380,7 +380,7 @@ describe("run session data", () => {
test("suppresses legacy bash part updates once shell events claim the call", () => {
let data = reduce(createSessionData(), {
type: "session.next.shell.started",
type: "shell.started",
properties: {
sessionID: "session-1",
timestamp: 1,
@@ -409,7 +409,7 @@ describe("run session data", () => {
).toEqual([])
data = reduce(data, {
type: "session.next.shell.ended",
type: "shell.ended",
properties: {
sessionID: "session-1",
timestamp: 2,
@@ -463,7 +463,7 @@ describe("run session data", () => {
expect(
reduce(data, {
type: "session.next.shell.started",
type: "shell.started",
properties: {
sessionID: "session-1",
timestamp: 1,
@@ -497,7 +497,7 @@ describe("run session data", () => {
expect(
reduce(data, {
type: "session.next.shell.ended",
type: "shell.ended",
properties: {
sessionID: "session-1",
timestamp: 2,
@@ -50,7 +50,7 @@ function ok<T>(data: T) {
}
function connected(id = "evt_connected") {
return { id, type: "server.connected", data: {} } satisfies RunV2Event
return { id, created: 0, type: "server.connected", data: {} } satisfies RunV2Event
}
function durable(sessionID: string, seq = 0, version = 1) {
@@ -125,22 +125,20 @@ function sdk(input: {
spyOn(client.v2.session, "switchModel").mockImplementation(() => ok(undefined))
// The generated methods have conditional return types for throwOnError; the
// minimal shapes below are enough for family discovery and model fallback.
spyOn(client.v2.session, "list").mockImplementation(
(request) => {
const parentID = request?.parentID
return ok({
location: { directory: "/tmp", project: { id: "proj_1", directory: "/tmp" } },
data:
input.sessions?.filter((session) =>
parentID === undefined
? true
: parentID === null
? session.parentID === undefined
: session.parentID === parentID,
) ?? [],
}) as never
},
)
spyOn(client.v2.session, "list").mockImplementation((request) => {
const parentID = request?.parentID
return ok({
location: { directory: "/tmp", project: { id: "proj_1", directory: "/tmp" } },
data:
input.sessions?.filter((session) =>
parentID === undefined
? true
: parentID === null
? session.parentID === undefined
: session.parentID === parentID,
) ?? [],
}) as never
})
spyOn(client.v2.model, "default").mockImplementation(
() =>
ok({
@@ -201,21 +199,19 @@ describe("V2 mini transport", () => {
while (!admitted) await Bun.sleep(0)
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: "hello" },
delivery: "steer",
inputID: "msg_prompt",
},
})
events.push({
id: "evt_text",
type: "session.next.text.delta",
created: 0,
type: "text.delta",
data: {
timestamp: 3,
sessionID: "ses_1",
assistantMessageID: "msg_assistant",
textID: "txt_1",
@@ -224,8 +220,9 @@ describe("V2 mini transport", () => {
})
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 4, sessionID: "ses_1", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
await turn
@@ -261,20 +258,19 @@ describe("V2 mini transport", () => {
queueMicrotask(() => {
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: input.prompt?.text ?? "" },
delivery: "steer",
inputID: "msg_prompt",
},
})
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
})
return ok({
@@ -356,20 +352,19 @@ describe("V2 mini transport", () => {
queueMicrotask(() => {
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: input.prompt?.text ?? "" },
delivery: "steer",
inputID: "msg_prompt",
},
})
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
})
return ok({
@@ -454,20 +449,19 @@ describe("V2 mini transport", () => {
queueMicrotask(() => {
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: input.prompt?.text ?? "" },
delivery: "steer",
inputID: "msg_prompt",
},
})
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
})
return ok({
@@ -528,6 +522,7 @@ describe("V2 mini transport", () => {
})
events.push({
id: "evt_permission",
created: 0,
type: "permission.v2.asked",
data: { id: "per_1", sessionID: "ses_1", action: "read", resources: ["/tmp/file"] },
})
@@ -728,9 +723,9 @@ describe("V2 mini transport", () => {
const replay = transport.replayOnResize({ localRows: () => [], reset: () => resetting })
events.push({
id: "evt_text",
type: "session.next.text.delta",
created: 0,
type: "text.delta",
data: {
timestamp: 3,
sessionID: "ses_1",
assistantMessageID: "msg_assistant",
textID: "txt_1",
@@ -813,10 +808,10 @@ describe("V2 mini transport", () => {
})
events.push({
id: "evt_reasoning",
type: "session.next.reasoning.ended",
created: 0,
type: "reasoning.ended",
durable: durable("ses_1"),
data: {
timestamp: 3,
sessionID: "ses_1",
assistantMessageID: "msg_assistant",
reasoningID: "reasoning_1",
@@ -869,8 +864,9 @@ describe("V2 mini transport", () => {
await transport.interruptActiveTurn()
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "interrupted" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
await turn
@@ -924,20 +920,19 @@ describe("V2 mini transport", () => {
while (!admitted) await Bun.sleep(0)
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: "hello" },
delivery: "steer",
inputID: "msg_prompt",
},
})
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
await turn
@@ -985,22 +980,21 @@ describe("V2 mini transport", () => {
while (!admitted) await Bun.sleep(0)
events.push({
id: "evt_prompted",
type: "session.next.prompted",
created: 0,
type: "prompt.promoted",
durable: durable("ses_1"),
data: {
timestamp: 2,
sessionID: "ses_1",
messageID: "msg_prompt",
prompt: { text: "hello" },
delivery: "steer",
inputID: "msg_prompt",
},
})
await Bun.sleep(0)
controller.abort()
events.push({
id: "evt_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_1", outcome: "interrupted" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_1", outcome: "success" },
})
await turn
@@ -1054,10 +1048,10 @@ describe("V2 mini transport", () => {
events.push({
id: "evt_child_step",
type: "session.next.step.started",
created: 0,
type: "step.started",
durable: durable("ses_child"),
data: {
timestamp: 2,
sessionID: "ses_child",
assistantMessageID: "msg_child_a",
agent: "explore",
@@ -1072,9 +1066,9 @@ describe("V2 mini transport", () => {
events.push({
id: "evt_child_text",
type: "session.next.text.delta",
created: 0,
type: "text.delta",
data: {
timestamp: 3,
sessionID: "ses_child",
assistantMessageID: "msg_child_a",
textID: "txt_child",
@@ -1086,8 +1080,9 @@ describe("V2 mini transport", () => {
events.push({
id: "evt_child_settled",
type: "session.next.execution.settled",
data: { timestamp: 4, sessionID: "ses_child", outcome: "success" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_child", outcome: "success" },
})
while (!states().some((state) => state.tabs.some((tab) => tab.status === "completed"))) await Bun.sleep(0)
await transport.close()
@@ -1130,10 +1125,10 @@ describe("V2 mini transport", () => {
// Both events arrive while session.get is still in flight.
events.push({
id: "evt_child_step",
type: "session.next.step.started",
created: 0,
type: "step.started",
durable: durable("ses_child"),
data: {
timestamp: 2,
sessionID: "ses_child",
assistantMessageID: "msg_child_a",
agent: "explore",
@@ -1142,8 +1137,9 @@ describe("V2 mini transport", () => {
})
events.push({
id: "evt_child_settled",
type: "session.next.execution.settled",
data: { timestamp: 3, sessionID: "ses_child", outcome: "interrupted" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_child", outcome: "interrupted" },
})
await Bun.sleep(0)
resolveGet?.()
@@ -1188,10 +1184,10 @@ describe("V2 mini transport", () => {
// Child event arrives first and gets buffered behind the gated session.get.
events.push({
id: "evt_child_step",
type: "session.next.step.started",
created: 0,
type: "step.started",
durable: durable("ses_child"),
data: {
timestamp: 2,
sessionID: "ses_child",
assistantMessageID: "msg_child_a",
agent: "explore",
@@ -1201,10 +1197,10 @@ describe("V2 mini transport", () => {
// Parent's background subagent tool.success adopts the child mid-discovery.
events.push({
id: "evt_parent_call",
type: "session.next.tool.called",
created: 0,
type: "tool.called",
durable: durable("ses_1"),
data: {
timestamp: 3,
sessionID: "ses_1",
assistantMessageID: "msg_parent_a",
callID: "call_sub",
@@ -1215,10 +1211,10 @@ describe("V2 mini transport", () => {
})
events.push({
id: "evt_parent_success",
type: "session.next.tool.success",
created: 0,
type: "tool.success",
durable: durable("ses_1", 1),
data: {
timestamp: 4,
sessionID: "ses_1",
assistantMessageID: "msg_parent_a",
callID: "call_sub",
@@ -1230,8 +1226,9 @@ describe("V2 mini transport", () => {
// The settled event arrives after adoption, so it applies directly.
events.push({
id: "evt_child_settled",
type: "session.next.execution.settled",
data: { timestamp: 5, sessionID: "ses_child", outcome: "interrupted" },
created: 0,
type: "execution.settled",
data: { sessionID: "ses_child", outcome: "interrupted" },
})
while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0)