mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 05:30:43 +00:00
125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createTalkSessionController, normalizeTalkTransport } from "./talk-session-controller.js";
|
|
|
|
function createController() {
|
|
return createTalkSessionController(
|
|
{
|
|
sessionId: "talk-session",
|
|
mode: "realtime",
|
|
transport: "gateway-relay",
|
|
brain: "agent-consult",
|
|
provider: "test",
|
|
maxRecentEvents: 3,
|
|
},
|
|
{ now: () => "2026-05-05T00:00:00.000Z" },
|
|
);
|
|
}
|
|
|
|
describe("createTalkSessionController", () => {
|
|
it("emits common envelopes and keeps bounded recent event history", () => {
|
|
const talk = createController();
|
|
|
|
talk.emit({ type: "session.started", payload: {} });
|
|
const firstTurn = talk.ensureTurn();
|
|
talk.emit({
|
|
type: "input.audio.delta",
|
|
turnId: firstTurn.turnId,
|
|
payload: { byteLength: 5 },
|
|
});
|
|
talk.emit({
|
|
type: "transcript.done",
|
|
turnId: firstTurn.turnId,
|
|
payload: { text: "hello" },
|
|
final: true,
|
|
});
|
|
|
|
expect(firstTurn.event).toMatchObject({
|
|
id: "talk-session:2",
|
|
type: "turn.started",
|
|
sessionId: "talk-session",
|
|
turnId: "turn-1",
|
|
mode: "realtime",
|
|
transport: "gateway-relay",
|
|
brain: "agent-consult",
|
|
provider: "test",
|
|
seq: 2,
|
|
timestamp: "2026-05-05T00:00:00.000Z",
|
|
});
|
|
expect(talk.recentEvents.map((event) => event.type)).toEqual([
|
|
"turn.started",
|
|
"input.audio.delta",
|
|
"transcript.done",
|
|
]);
|
|
});
|
|
|
|
it("rejects stale turn completion before clearing the active turn", () => {
|
|
const talk = createController();
|
|
talk.ensureTurn({ turnId: "turn-old" });
|
|
expect(talk.endTurn({ turnId: "turn-other" })).toEqual({
|
|
ok: false,
|
|
reason: "stale_turn",
|
|
});
|
|
expect(talk.activeTurnId).toBe("turn-old");
|
|
|
|
const ended = talk.endTurn({ turnId: "turn-old", payload: { reason: "done" } });
|
|
|
|
expect(ended).toMatchObject({
|
|
ok: true,
|
|
turnId: "turn-old",
|
|
event: {
|
|
type: "turn.ended",
|
|
turnId: "turn-old",
|
|
payload: { reason: "done" },
|
|
final: true,
|
|
},
|
|
});
|
|
expect(talk.activeTurnId).toBeUndefined();
|
|
});
|
|
|
|
it("tracks output audio lifecycle without duplicate started events", () => {
|
|
const talk = createController();
|
|
|
|
const first = talk.startOutputAudio({ payload: { callId: "call-1" } });
|
|
const second = talk.startOutputAudio({ payload: { callId: "call-1" } });
|
|
const done = talk.finishOutputAudio({ payload: { reason: "mark" } });
|
|
|
|
expect(first.event).toMatchObject({
|
|
type: "output.audio.started",
|
|
turnId: "turn-1",
|
|
});
|
|
expect(second).toEqual({ turnId: "turn-1" });
|
|
expect(done).toMatchObject({
|
|
type: "output.audio.done",
|
|
turnId: "turn-1",
|
|
payload: { reason: "mark" },
|
|
final: true,
|
|
});
|
|
expect(talk.outputAudioActive).toBe(false);
|
|
});
|
|
|
|
it("clears stale output audio state when a replacement turn starts", () => {
|
|
const talk = createController();
|
|
|
|
talk.startOutputAudio({ turnId: "turn-old" });
|
|
expect(talk.outputAudioActive).toBe(true);
|
|
|
|
const current = talk.startTurn({ turnId: "turn-current" });
|
|
|
|
expect(current).toMatchObject({
|
|
turnId: "turn-current",
|
|
event: expect.objectContaining({ type: "turn.started", turnId: "turn-current" }),
|
|
});
|
|
expect(talk.activeTurnId).toBe("turn-current");
|
|
expect(talk.outputAudioActive).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("normalizeTalkTransport", () => {
|
|
it("maps legacy public transport names to canonical names", () => {
|
|
expect(normalizeTalkTransport(undefined)).toBeUndefined();
|
|
expect(normalizeTalkTransport("webrtc-sdp")).toBe("webrtc");
|
|
expect(normalizeTalkTransport("json-pcm-websocket")).toBe("provider-websocket");
|
|
expect(normalizeTalkTransport("gateway-relay")).toBe("gateway-relay");
|
|
});
|
|
});
|