mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-11 12:23:28 +00:00
refactor(schema): rename V2 session events and normalize payloads (#35217)
This commit is contained in:
parent
4790a2772c
commit
394e0b9045
63 changed files with 1939 additions and 2040 deletions
|
|
@ -156,17 +156,16 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
|
||||
const time = "timestamp" in event.data ? toMillis(event.data.timestamp) : Date.now()
|
||||
const time = toMillis(event.created)
|
||||
|
||||
if (event.type === "session.next.prompted") {
|
||||
if (event.data.messageID === messageID) {
|
||||
if (event.type === "prompt.promoted") {
|
||||
if (event.data.inputID === messageID) {
|
||||
promoted = true
|
||||
continue
|
||||
}
|
||||
if (promoted && event.data.delivery === "queue") return
|
||||
}
|
||||
if (
|
||||
event.type === "session.next.execution.settled" &&
|
||||
event.type === "execution.settled" &&
|
||||
event.data.outcome === "interrupted" &&
|
||||
(interrupted || permissionRejected || questionRejected || formCancelled)
|
||||
) {
|
||||
|
|
@ -174,7 +173,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
}
|
||||
if (!promoted) continue
|
||||
|
||||
if (event.type === "session.next.step.started") {
|
||||
if (event.type === "step.started") {
|
||||
const part: StepStartPart = {
|
||||
id: partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -190,11 +189,11 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "session.next.text.started") {
|
||||
if (event.type === "text.started") {
|
||||
starts.set(event.data.textID, { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.text.ended") {
|
||||
if (event.type === "text.ended") {
|
||||
const started = starts.get(event.data.textID)
|
||||
const part: TextPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
|
|
@ -208,11 +207,11 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "session.next.reasoning.started") {
|
||||
if (event.type === "reasoning.started") {
|
||||
starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.reasoning.ended" && input.thinking) {
|
||||
if (event.type === "reasoning.ended" && input.thinking) {
|
||||
const started = starts.get(event.data.reasoningID)
|
||||
const part: ReasoningPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
|
|
@ -237,7 +236,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "session.next.tool.input.started") {
|
||||
if (event.type === "tool.input.started") {
|
||||
tools.set(event.data.callID, {
|
||||
id: partID(event.id),
|
||||
timestamp: time,
|
||||
|
|
@ -247,12 +246,12 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
})
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.tool.input.ended") {
|
||||
if (event.type === "tool.input.ended") {
|
||||
const current = tools.get(event.data.callID)
|
||||
if (current) current.raw = event.data.text
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.tool.called") {
|
||||
if (event.type === "tool.called") {
|
||||
const current = tools.get(event.data.callID)
|
||||
tools.set(event.data.callID, {
|
||||
id: current?.id ?? partID(event.id),
|
||||
|
|
@ -265,7 +264,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
})
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.tool.success") {
|
||||
if (event.type === "tool.success") {
|
||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
||||
const part: ToolPart = {
|
||||
id: current.id,
|
||||
|
|
@ -298,7 +297,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
if (!emit("tool_use", time, { part })) await input.renderTool(part)
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.tool.failed") {
|
||||
if (event.type === "tool.failed") {
|
||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
||||
const error = event.data.error.message
|
||||
const part: ToolPart = {
|
||||
|
|
@ -329,7 +328,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
continue
|
||||
}
|
||||
|
||||
if (event.type === "session.next.step.ended") {
|
||||
if (event.type === "step.ended") {
|
||||
const part: StepFinishPart = {
|
||||
id: partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
|
|
@ -343,19 +342,19 @@ export async function runNonInteractivePrompt(input: Input) {
|
|||
emit("step_finish", time, { part })
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.step.failed") {
|
||||
if (event.type === "step.failed") {
|
||||
if (interrupted || permissionRejected || questionRejected || formCancelled) continue
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.next.execution.settled") {
|
||||
if (event.type === "execution.settled") {
|
||||
if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) {
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
const error = event.data.error ?? { type: "unknown", message: "Session execution failed" }
|
||||
if (!emit("error", toMillis(event.data.timestamp), { error })) UI.error(error.message)
|
||||
if (!emit("error", time, { error })) UI.error(error.message)
|
||||
}
|
||||
if (event.data.outcome === "interrupted" && interrupted) process.exitCode = 130
|
||||
return
|
||||
|
|
@ -458,11 +457,12 @@ function partID(eventID: string) {
|
|||
|
||||
function fallbackTool(event: {
|
||||
id: string
|
||||
data: { timestamp: number; assistantMessageID: string; callID: string }
|
||||
created: number
|
||||
data: { assistantMessageID: string; callID: string }
|
||||
}): ToolState {
|
||||
return {
|
||||
id: partID(event.id),
|
||||
timestamp: toMillis(event.data.timestamp),
|
||||
timestamp: toMillis(event.created),
|
||||
assistantMessageID: event.data.assistantMessageID,
|
||||
tool: "tool",
|
||||
input: {},
|
||||
|
|
|
|||
|
|
@ -728,7 +728,7 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
|
|||
const data = input.data
|
||||
const event = input.event
|
||||
|
||||
if (event.type === "session.next.shell.started") {
|
||||
if (event.type === "shell.started") {
|
||||
if (event.properties.sessionID !== input.sessionID) {
|
||||
return out(data, commits)
|
||||
}
|
||||
|
|
@ -748,7 +748,7 @@ export function reduceSessionData(input: SessionDataInput): SessionDataOutput {
|
|||
return out(data, commits, patch({ status: "running shell" }))
|
||||
}
|
||||
|
||||
if (event.type === "session.next.shell.ended") {
|
||||
if (event.type === "shell.ended") {
|
||||
if (event.properties.sessionID !== input.sessionID) {
|
||||
return out(data, commits)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -424,21 +424,21 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
}
|
||||
|
||||
const reduce = (child: ChildState, event: V2Event) => {
|
||||
if (event.type === "session.next.prompted") {
|
||||
if (userFrame(child, event.data.messageID, event.data.prompt.text)) {
|
||||
touch(child, event.data.timestamp)
|
||||
if (event.type === "prompt.promoted") {
|
||||
if (userFrame(child, event.data.inputID, "")) {
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.step.started") {
|
||||
touch(child, event.data.timestamp)
|
||||
if (event.type === "step.started") {
|
||||
touch(child, event.created)
|
||||
if (child.label === FALLBACK_LABEL && event.data.agent) child.label = Locale.titlecase(event.data.agent)
|
||||
if (child.status !== "running") child.status = "running"
|
||||
input.emit()
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.text.delta") {
|
||||
if (event.type === "text.delta") {
|
||||
const projected = child.projectedText.get(event.data.textID)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
|
|
@ -455,11 +455,11 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
})
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.text.ended") {
|
||||
if (event.type === "text.ended") {
|
||||
child.text.set(event.data.textID, event.data.text)
|
||||
child.projectedText.delete(event.data.textID)
|
||||
setFrame(child, `text:${event.data.textID}`, {
|
||||
|
|
@ -470,11 +470,11 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
})
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.reasoning.delta") {
|
||||
if (event.type === "reasoning.delta") {
|
||||
const projected = child.projectedReasoning.get(event.data.reasoningID)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
|
|
@ -495,7 +495,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.reasoning.ended") {
|
||||
if (event.type === "reasoning.ended") {
|
||||
child.reasoning.set(event.data.reasoningID, event.data.text)
|
||||
child.projectedReasoning.delete(event.data.reasoningID)
|
||||
if (!input.thinking) return
|
||||
|
|
@ -510,16 +510,16 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.input.started") {
|
||||
child.tools.set(event.data.callID, { name: event.data.name, input: {}, started: event.data.timestamp })
|
||||
if (event.type === "tool.input.started") {
|
||||
child.tools.set(event.data.callID, { name: event.data.name, input: {}, started: event.created })
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.called") {
|
||||
if (event.type === "tool.called") {
|
||||
const current = child.tools.get(event.data.callID)
|
||||
child.tools.set(event.data.callID, {
|
||||
name: event.data.tool,
|
||||
input: event.data.input,
|
||||
started: current?.started ?? event.data.timestamp,
|
||||
started: current?.started ?? event.created,
|
||||
})
|
||||
childTool(
|
||||
child,
|
||||
|
|
@ -529,18 +529,18 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
name: event.data.tool,
|
||||
provider: event.data.provider,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
time: { created: current?.started ?? event.data.timestamp, ran: event.data.timestamp },
|
||||
time: { created: current?.started ?? event.created, ran: event.created },
|
||||
},
|
||||
event.data.assistantMessageID,
|
||||
)
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.success" || event.type === "session.next.tool.failed") {
|
||||
if (event.type === "tool.success" || event.type === "tool.failed") {
|
||||
if (child.finishedTools.has(event.data.callID)) return
|
||||
const current = child.tools.get(event.data.callID)
|
||||
const failed = event.type === "session.next.tool.failed"
|
||||
const failed = event.type === "tool.failed"
|
||||
childTool(
|
||||
child,
|
||||
{
|
||||
|
|
@ -566,18 +566,18 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
result: event.data.result,
|
||||
},
|
||||
time: {
|
||||
created: current?.started ?? event.data.timestamp,
|
||||
created: current?.started ?? event.created,
|
||||
ran: current?.started,
|
||||
completed: event.data.timestamp,
|
||||
completed: event.created,
|
||||
},
|
||||
},
|
||||
event.data.assistantMessageID,
|
||||
)
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.step.failed") {
|
||||
if (event.type === "step.failed") {
|
||||
setFrame(child, `error:step:${event.data.assistantMessageID}`, {
|
||||
kind: "error",
|
||||
source: "system",
|
||||
|
|
@ -585,14 +585,14 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
phase: "start",
|
||||
messageID: event.data.assistantMessageID,
|
||||
})
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.execution.settled") {
|
||||
if (event.type === "execution.settled") {
|
||||
child.status =
|
||||
event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error"
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
input.emit()
|
||||
}
|
||||
}
|
||||
|
|
@ -613,15 +613,15 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
|
||||
return {
|
||||
main(event) {
|
||||
if (event.type === "session.next.tool.called") {
|
||||
if (event.type === "tool.called") {
|
||||
if (event.data.tool === "subagent") pendingCalls.set(event.data.callID, event.data.input)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.failed") {
|
||||
if (event.type === "tool.failed") {
|
||||
pendingCalls.delete(event.data.callID)
|
||||
return
|
||||
}
|
||||
if (event.type !== "session.next.tool.success") return
|
||||
if (event.type !== "tool.success") return
|
||||
const pending = pendingCalls.get(event.data.callID)
|
||||
pendingCalls.delete(event.data.callID)
|
||||
const found = childSessionID(record(event.data.structured))
|
||||
|
|
@ -633,7 +633,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
|||
child.status = "running"
|
||||
}
|
||||
if (!found.running && child.status === "running") child.status = "completed"
|
||||
touch(child, event.data.timestamp)
|
||||
touch(child, event.created)
|
||||
input.emit()
|
||||
if (!child.hydrated) void hydrateChild(child)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -294,7 +294,14 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
if (!state.tools.get(item.id)?.running) write([toolCommit(part, "start")])
|
||||
state.finishedTools.add(item.id)
|
||||
state.tools.delete(item.id)
|
||||
write([toolCommit(part, item.state.status === "completed" && part.state.status === "completed" && part.state.output ? "progress" : "final")])
|
||||
write([
|
||||
toolCommit(
|
||||
part,
|
||||
item.state.status === "completed" && part.state.status === "completed" && part.state.output
|
||||
? "progress"
|
||||
: "final",
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
const renderMessage = (message: SessionMessage, render: boolean, reuseVisibleWait: boolean) => {
|
||||
|
|
@ -395,17 +402,17 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
}
|
||||
input.trace?.write("recv.event", event)
|
||||
subagents.main(event)
|
||||
if (event.type === "session.next.prompted") {
|
||||
if (state.wait?.messageID === event.data.messageID) state.wait.promoted = true
|
||||
state.messageIDs.add(event.data.messageID)
|
||||
if (event.type === "prompt.promoted") {
|
||||
if (state.wait?.messageID === event.data.inputID) state.wait.promoted = true
|
||||
state.messageIDs.add(event.data.inputID)
|
||||
write([], { phase: "running", status: "waiting for assistant" })
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.step.started") {
|
||||
if (event.type === "step.started") {
|
||||
write([], { phase: "running", status: "assistant responding" })
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.text.delta") {
|
||||
if (event.type === "text.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const projected = state.projectedText.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
|
|
@ -427,7 +434,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.text.ended") {
|
||||
if (event.type === "text.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const previous = state.text.get(key) ?? ""
|
||||
if (event.data.text.length > previous.length)
|
||||
|
|
@ -445,7 +452,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
state.projectedText.delete(key)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.reasoning.delta") {
|
||||
if (event.type === "reasoning.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const projected = state.projectedReasoning.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
|
|
@ -468,7 +475,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.reasoning.ended") {
|
||||
if (event.type === "reasoning.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const previous = state.reasoning.get(key) ?? ""
|
||||
if (input.thinking && event.data.text.length > previous.length)
|
||||
|
|
@ -486,17 +493,17 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
state.projectedReasoning.delete(key)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.input.started") {
|
||||
if (event.type === "tool.input.started") {
|
||||
state.tools.set(event.data.callID, {
|
||||
messageID: event.data.assistantMessageID,
|
||||
name: event.data.name,
|
||||
input: {},
|
||||
started: event.data.timestamp,
|
||||
started: event.created,
|
||||
running: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.called") {
|
||||
if (event.type === "tool.called") {
|
||||
if (state.finishedTools.has(event.data.callID)) return
|
||||
const current = state.tools.get(event.data.callID)
|
||||
const item: SessionMessageAssistantTool = {
|
||||
|
|
@ -505,22 +512,29 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
name: event.data.tool,
|
||||
provider: event.data.provider,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
time: { created: current?.started ?? event.data.timestamp, ran: event.data.timestamp },
|
||||
time: { created: current?.started ?? event.created, ran: event.created },
|
||||
}
|
||||
renderTool(event.data.assistantMessageID, item)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.tool.progress") return
|
||||
if (event.type === "session.next.tool.success" || event.type === "session.next.tool.failed") {
|
||||
if (event.type === "tool.progress") return
|
||||
if (event.type === "tool.success" || event.type === "tool.failed") {
|
||||
const current = state.tools.get(event.data.callID)
|
||||
const failed = event.type === "session.next.tool.failed"
|
||||
const failed = event.type === "tool.failed"
|
||||
const item: SessionMessageAssistantTool = {
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: current?.name ?? "tool",
|
||||
provider: event.data.provider,
|
||||
state: failed
|
||||
? { status: "error", input: current?.input ?? {}, structured: {}, content: [], error: event.data.error, result: event.data.result }
|
||||
? {
|
||||
status: "error",
|
||||
input: current?.input ?? {},
|
||||
structured: {},
|
||||
content: [],
|
||||
error: event.data.error,
|
||||
result: event.data.result,
|
||||
}
|
||||
: {
|
||||
status: "completed",
|
||||
input: current?.input ?? {},
|
||||
|
|
@ -529,7 +543,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
outputPaths: event.data.outputPaths,
|
||||
result: event.data.result,
|
||||
},
|
||||
time: { created: current?.started ?? event.data.timestamp, ran: current?.started, completed: event.data.timestamp },
|
||||
time: { created: current?.started ?? event.created, ran: current?.started, completed: event.created },
|
||||
}
|
||||
renderTool(event.data.assistantMessageID, item)
|
||||
return
|
||||
|
|
@ -554,7 +568,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
syncBlockers()
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.step.ended") {
|
||||
if (event.type === "step.ended") {
|
||||
const total =
|
||||
event.data.tokens.input +
|
||||
event.data.tokens.output +
|
||||
|
|
@ -562,16 +576,19 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
event.data.tokens.cache.read +
|
||||
event.data.tokens.cache.write
|
||||
const usage = total > 0 ? total.toLocaleString() : ""
|
||||
write([], { phase: event.data.finish === "tool-calls" ? "running" : "idle", usage: event.data.cost ? `${usage} · ${money.format(event.data.cost)}` : usage })
|
||||
write([], {
|
||||
phase: event.data.finish === "tool-calls" ? "running" : "idle",
|
||||
usage: event.data.cost ? `${usage} · ${money.format(event.data.cost)}` : usage,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.step.failed") {
|
||||
if (event.type === "step.failed") {
|
||||
state.errors.add(event.data.assistantMessageID)
|
||||
if (state.wait) state.wait.failureRendered = true
|
||||
write([{ kind: "error", source: "system", text: errorMessage(event.data.error), phase: "start" }])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.next.execution.settled") {
|
||||
if (event.type === "execution.settled") {
|
||||
write([], { phase: "idle", status: "" })
|
||||
const current = state.wait
|
||||
if (!current || (!current.promoted && !current.interrupted)) return
|
||||
|
|
@ -611,7 +628,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
sseMaxRetryAttempts: 0,
|
||||
throwOnError: true,
|
||||
})
|
||||
const stream = response.stream[Symbol.asyncIterator]() as AsyncGenerator<RunV2Event>
|
||||
const stream = response.stream[Symbol.asyncIterator]() as AsyncGenerator<RunV2Event>
|
||||
try {
|
||||
const first = await stream.next()
|
||||
if (first.done || first.value.type !== "server.connected") throw new Error("Event stream disconnected")
|
||||
|
|
@ -689,10 +706,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
]
|
||||
: [],
|
||||
)
|
||||
const attachments = [
|
||||
...prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
|
||||
...promptFiles,
|
||||
]
|
||||
const attachments = [...prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])), ...promptFiles]
|
||||
const agents = next.prompt.parts.flatMap((part) =>
|
||||
part.type === "agent"
|
||||
? [
|
||||
|
|
@ -735,10 +749,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
|||
sessionID: input.sessionID,
|
||||
id: messageID,
|
||||
prompt: {
|
||||
text: [
|
||||
next.prompt.text,
|
||||
...prepared.flatMap((file) => (file.text ? [file.text] : [])),
|
||||
].join("\n\n"),
|
||||
text: [next.prompt.text, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
||||
files: attachments.length ? attachments : undefined,
|
||||
agents: agents.length ? agents : undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ test.skip("step snapshots carry over to assistant messages", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.step.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
agent: "build",
|
||||
model: {
|
||||
id: ModelV2.ID.make("model"),
|
||||
|
|
@ -43,12 +43,12 @@ test.skip("step snapshots carry over to assistant messages", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.step.ended",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.ended",
|
||||
durable: durable(sessionID, 1, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
finish: "stop",
|
||||
cost: 0,
|
||||
tokens: {
|
||||
|
|
@ -76,12 +76,12 @@ test.skip("text ended populates assistant text content", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.step.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
agent: "build",
|
||||
model: {
|
||||
id: ModelV2.ID.make("model"),
|
||||
|
|
@ -95,12 +95,12 @@ test.skip("text ended populates assistant text content", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.text.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "text.started",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
textID: "text-1",
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
|
|
@ -109,12 +109,12 @@ test.skip("text ended populates assistant text content", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.text.ended",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "text.ended",
|
||||
durable: durable(sessionID, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(3),
|
||||
textID: "text-1",
|
||||
text: "hello assistant",
|
||||
},
|
||||
|
|
@ -135,12 +135,12 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.step.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "step.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
agent: "build",
|
||||
model: {
|
||||
id: ModelV2.ID.make("model"),
|
||||
|
|
@ -154,12 +154,12 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.tool.input.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.input.started",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
callID,
|
||||
name: "bash",
|
||||
},
|
||||
|
|
@ -169,12 +169,12 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.tool.called",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.called",
|
||||
durable: durable(sessionID, 2),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(3),
|
||||
callID,
|
||||
tool: "bash",
|
||||
input: { command: "pwd" },
|
||||
|
|
@ -186,12 +186,12 @@ test.skip("tool completion stores completed timestamp", () => {
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.tool.success",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "tool.success",
|
||||
durable: durable(sessionID, 3),
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
timestamp: DateTime.makeUnsafe(4),
|
||||
callID,
|
||||
structured: {},
|
||||
content: [{ type: "text", text: "/tmp" }],
|
||||
|
|
@ -212,17 +212,16 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
const state: SessionMessageUpdater.MemoryState = { messages: [] }
|
||||
const sessionID = SessionID.make("session")
|
||||
const id = EventV2.ID.create()
|
||||
const compactionID = SessionMessage.ID.create()
|
||||
const endedID = EventV2.ID.create()
|
||||
|
||||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id,
|
||||
type: "session.next.compaction.started",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.started",
|
||||
durable: durable(sessionID),
|
||||
data: {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(1),
|
||||
reason: "auto",
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
|
|
@ -233,11 +232,10 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.compaction.delta",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.delta",
|
||||
data: {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(2),
|
||||
text: "hello ",
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
|
|
@ -246,11 +244,10 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.compaction.delta",
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.delta",
|
||||
data: {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(3),
|
||||
text: "summary",
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
|
|
@ -258,13 +255,12 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
|
||||
Effect.runSync(
|
||||
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
|
||||
id: EventV2.ID.create(),
|
||||
type: "session.next.compaction.ended",
|
||||
id: endedID,
|
||||
created: DateTime.makeUnsafe(0),
|
||||
type: "compaction.ended",
|
||||
durable: durable(sessionID, 1),
|
||||
data: {
|
||||
sessionID,
|
||||
messageID: compactionID,
|
||||
timestamp: DateTime.makeUnsafe(4),
|
||||
reason: "auto",
|
||||
text: "final summary",
|
||||
recent: "recent context",
|
||||
|
|
@ -274,11 +270,11 @@ test("compaction events reduce to compaction message only when completed", () =>
|
|||
|
||||
expect(state.messages).toHaveLength(1)
|
||||
expect(state.messages[0]).toMatchObject({
|
||||
id: compactionID,
|
||||
id: SessionMessage.ID.fromEvent(endedID),
|
||||
type: "compaction",
|
||||
reason: "auto",
|
||||
summary: "final summary",
|
||||
recent: "recent context",
|
||||
time: { created: DateTime.makeUnsafe(4) },
|
||||
time: { created: DateTime.makeUnsafe(0) },
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue