refactor(schema): rename V2 session events and normalize payloads (#35217)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user