opencode/packages/voice/test/protocol.test.ts
2026-07-28 21:00:35 -04:00

137 lines
5.1 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { Option } from "effect"
import { decodeVoiceToolInput } from "../src/protocol"
import { createLiveContextAppendQueue, liveNotificationAcknowledgement } from "../src/protocol-live"
import { realtimeNotificationAcknowledgement, realtimeSessionUpdate } from "../src/protocol-realtime"
import { createSingleFlightAcknowledgement } from "../src/single-flight-acknowledgement"
import { openCodeAnnouncementText } from "../src/opencode-notification"
describe("voice protocol", () => {
test("accepts only JSON object tool arguments", () => {
expect(Option.getOrUndefined(decodeVoiceToolInput('{"text":"hello","nested":{"value":1}}'))).toEqual({
text: "hello",
nested: { value: 1 },
})
for (const input of ["not json", "null", "[]", "1", '"text"'])
expect(Option.getOrUndefined(decodeVoiceToolInput(input))).toBeUndefined()
})
test("correlates Realtime notification acknowledgements", () => {
const pending = { itemID: "item-current", eventID: "event-current" }
expect(
realtimeNotificationAcknowledgement(
{ type: "conversation.item.created", item: { id: "item-current" } },
pending,
),
).toBe(true)
expect(
realtimeNotificationAcknowledgement(
{ type: "conversation.item.created", item: { id: "item-other" } },
pending,
),
).toBeUndefined()
expect(
realtimeNotificationAcknowledgement(
{ type: "error", error: { event_id: "event-current" } },
pending,
),
).toBe(false)
})
test("preserves tool schemas in Realtime session updates", () => {
const tools = [
{
type: "function" as const,
name: "open_form",
description: "Open form",
parameters: {
type: "object",
properties: { answer: { type: "object", additionalProperties: true } },
additionalProperties: true,
},
},
]
expect(JSON.parse(JSON.stringify(realtimeSessionUpdate({ instructions: "test", tools, voice: "marin" }))).session.tools)
.toEqual(tools)
})
test("correlates Live notification acknowledgements by expected context", () => {
const pending = { eventID: "event-current", acknowledgement: "delegation.context.appended" as const }
expect(
liveNotificationAcknowledgement(
{ type: "delegation.context.appended", eventID: "event-current" },
pending,
),
).toBe(true)
expect(
liveNotificationAcknowledgement({ type: "session.context.appended", eventID: "event-current" }, pending),
).toBeUndefined()
expect(
liveNotificationAcknowledgement(
{ type: "delegation.context.appended", eventID: "event-other" },
pending,
),
).toBe(true)
expect(
liveNotificationAcknowledgement({ type: "error", errorEventID: "event-current" }, pending),
).toBe(false)
})
test("serializes indistinguishable Live context appends", async () => {
const sent: Array<Record<string, unknown>> = []
const queue = createLiveContextAppendQueue({
send: (event) => {
sent.push(event)
return true
},
timeoutMs: 100,
})
const first = queue.append(
{ type: "delegation.context.append", event_id: "event-1" },
"delegation.context.appended",
)
const second = queue.append(
{ type: "delegation.context.append", event_id: "event-2" },
"delegation.context.appended",
)
expect(sent.map((event) => event["event_id"])).toEqual(["event-1"])
queue.receive({ type: "session.context.appended", eventID: "unrelated" })
expect(sent.map((event) => event["event_id"])).toEqual(["event-1"])
queue.receive({ type: "delegation.context.appended", eventID: "not-echoed" })
expect(await first).toBe(true)
expect(sent.map((event) => event["event_id"])).toEqual(["event-1", "event-2"])
queue.receive({ type: "delegation.context.appended" })
expect(await second).toBe(true)
queue.close()
})
test("keeps notification acknowledgement single-flight", async () => {
const acknowledgement = createSingleFlightAcknowledgement<{ readonly token: string }>(50)
const first = acknowledgement.begin("notification-1", { token: "first" })
const second = acknowledgement.begin("notification-2", { token: "second" })
expect(first.started).toBe(true)
expect(second.started).toBe(false)
expect(await second.promise).toBe(false)
expect(acknowledgement.current()).toEqual({ id: "notification-1", correlation: { token: "first" } })
acknowledgement.settle("notification-1", true)
expect(await first.promise).toBe(true)
expect(acknowledgement.current()).toBeUndefined()
})
test("renders bounded conversational OpenCode announcements without identifiers", () => {
const text = openCodeAnnouncementText({
type: "opencode.prompt.completed",
session_id: "ses_private",
prompt_id: "msg_private",
status: "completed",
text: "finished ".repeat(200),
})
expect(text.length).toBeLessThanOrEqual(800)
expect(text).not.toContain("ses_private")
expect(text).not.toContain("msg_private")
expect(text).toStartWith("OpenCode prompt completed: finished")
})
})