fix(sync): preserve part update ordering (#1167)
* fix(sync): preserve part update ordering * test(sync): wait for pipeline delivery --------- Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
This commit is contained in:
committed by
GitHub
co-authored by
Isaac Sanchez
parent
afe86a84b3
commit
9715b6c23c
@@ -0,0 +1,99 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { Event, OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { createEventPipeline } from "./event-pipeline"
|
||||||
|
|
||||||
|
const failAfter = (ms: number) => new Promise<never>((_, reject) => {
|
||||||
|
setTimeout(() => reject(new Error("Timed out waiting for event pipeline flush")), ms)
|
||||||
|
})
|
||||||
|
|
||||||
|
function partUpdatedEvent(text: string): Event {
|
||||||
|
return {
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "prt_1",
|
||||||
|
messageID: "msg_1",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
type: "text",
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Event
|
||||||
|
}
|
||||||
|
|
||||||
|
function deltaEvent(delta: string): Event {
|
||||||
|
return {
|
||||||
|
type: "message.part.delta",
|
||||||
|
properties: {
|
||||||
|
messageID: "msg_1",
|
||||||
|
partID: "prt_1",
|
||||||
|
field: "text",
|
||||||
|
delta,
|
||||||
|
},
|
||||||
|
} as Event
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSdk(events: Event[], streamFinished: () => void): OpencodeClient {
|
||||||
|
return {
|
||||||
|
global: {
|
||||||
|
event: async ({ signal }: { signal: AbortSignal }) => ({
|
||||||
|
stream: (async function* () {
|
||||||
|
for (const payload of events) {
|
||||||
|
yield { directory: "/repo", payload }
|
||||||
|
}
|
||||||
|
streamFinished()
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signal.addEventListener("abort", () => resolve(), { once: true })
|
||||||
|
})
|
||||||
|
})(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as unknown as OpencodeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("createEventPipeline", () => {
|
||||||
|
test("preserves part update order around text deltas", async () => {
|
||||||
|
let resolveStreamFinished!: () => void
|
||||||
|
const streamFinished = new Promise<void>((resolve) => {
|
||||||
|
resolveStreamFinished = resolve
|
||||||
|
})
|
||||||
|
let resolveDelivered!: () => void
|
||||||
|
const deliveredAll = new Promise<void>((resolve) => {
|
||||||
|
resolveDelivered = resolve
|
||||||
|
})
|
||||||
|
const delivered: Event[] = []
|
||||||
|
const pipeline = createEventPipeline({
|
||||||
|
sdk: createSdk([
|
||||||
|
partUpdatedEvent("a"),
|
||||||
|
deltaEvent("b"),
|
||||||
|
partUpdatedEvent("ab"),
|
||||||
|
], resolveStreamFinished),
|
||||||
|
onEvent: (_directory, payload) => {
|
||||||
|
delivered.push(payload)
|
||||||
|
if (delivered.length === 3) {
|
||||||
|
resolveDelivered()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transport: "sse",
|
||||||
|
heartbeatTimeoutMs: 1_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await streamFinished
|
||||||
|
await Promise.race([deliveredAll, failAfter(500)])
|
||||||
|
} finally {
|
||||||
|
pipeline.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(delivered.map((event) => {
|
||||||
|
if (event.type === "message.part.delta") {
|
||||||
|
return `delta:${(event.properties as { delta: string }).delta}`
|
||||||
|
}
|
||||||
|
return `updated:${((event.properties as { part: { text: string } }).part).text}`
|
||||||
|
})).toEqual(["updated:a", "delta:b", "updated:ab"])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -211,10 +211,6 @@ export function createEventPipeline(input: EventPipelineInput) {
|
|||||||
if (payload.type === "lsp.updated") {
|
if (payload.type === "lsp.updated") {
|
||||||
return "lsp.updated"
|
return "lsp.updated"
|
||||||
}
|
}
|
||||||
if (payload.type === "message.part.updated") {
|
|
||||||
const part = (payload.properties as { part: { messageID: string; id: string } }).part
|
|
||||||
return `message.part.updated:${part.messageID}:${part.id}`
|
|
||||||
}
|
|
||||||
if (payload.type === "message.part.delta") {
|
if (payload.type === "message.part.delta") {
|
||||||
const props = payload.properties as { messageID: string; partID: string; field: string }
|
const props = payload.properties as { messageID: string; partID: string; field: string }
|
||||||
return `message.part.delta:${props.messageID}:${props.partID}:${props.field}`
|
return `message.part.delta:${props.messageID}:${props.partID}:${props.field}`
|
||||||
|
|||||||
Reference in New Issue
Block a user