forked from openclaw/openclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathturn-context-tracker.test.ts
More file actions
150 lines (120 loc) · 5.08 KB
/
Copy pathturn-context-tracker.test.ts
File metadata and controls
150 lines (120 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { describe, expect, it } from "vitest";
import { createRealtimeVoiceTurnContextTracker } from "./turn-context-tracker.js";
describe("realtime voice turn context tracker", () => {
it("consumes audio contexts and prunes silent closed turns", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>();
const silent = tracker.open({ id: "silent" });
const spoken = tracker.open({ id: "spoken" });
tracker.close(silent);
tracker.markAudio(spoken);
expect(tracker.size()).toBe(1);
expect(tracker.consumeAudioContext()).toEqual({ id: "spoken" });
expect(tracker.consumeAudioContext()).toBeUndefined();
});
it("marks consumed handles closed when callers close them later", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>();
const turn = tracker.open({ id: "speaker" });
tracker.markAudio(turn);
expect(tracker.consumeAudioContext()).toEqual({ id: "speaker" });
tracker.close(turn);
expect(turn.closed).toBe(true);
});
it("can defer retaining silent turns until audio starts", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>({
deferUntilAudio: true,
});
const silent = tracker.open({ id: "silent" });
const spoken = tracker.open({ id: "spoken" });
expect(tracker.size()).toBe(0);
tracker.close(silent);
tracker.markAudio(spoken);
expect(tracker.size()).toBe(1);
expect(tracker.consumeAudioContext()).toEqual({ id: "spoken" });
expect(tracker.consumeAudioContext()).toBeUndefined();
});
it("ignores handles from another tracker", () => {
const first = createRealtimeVoiceTurnContextTracker<{ id: string }>();
const second = createRealtimeVoiceTurnContextTracker<{ id: string }>();
const firstTurn = first.open({ id: "first" });
second.markAudio(firstTurn);
second.close(firstTurn);
expect(firstTurn.hasAudio).toBe(false);
expect(firstTurn.closed).toBe(false);
expect(first.consumeAudioContext()).toBeUndefined();
});
it("drops closed audio turns that are older than later audio", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>();
const older = tracker.open({ id: "older" });
tracker.markAudio(older);
tracker.close(older);
const later = tracker.open({ id: "later" });
tracker.markAudio(later);
expect(tracker.consumeAudioContext()).toEqual({ id: "later" });
expect(tracker.consumeAudioContext()).toBeUndefined();
});
it("retains caller-owned turn stats on peeked audio turns", () => {
const tracker = createRealtimeVoiceTurnContextTracker<
{ id: string },
{ chunks: number; interruptedPlayback: boolean }
>();
const turn = tracker.open({ id: "speaker" }, { chunks: 0, interruptedPlayback: false });
tracker.markAudio(turn);
turn.chunks += 1;
expect(tracker.peekAudioTurn()).toMatchObject({
context: { id: "speaker" },
chunks: 1,
interruptedPlayback: false,
hasAudio: true,
});
});
it("bounds retained turn handles", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>({ limit: 2 });
const first = tracker.open({ id: "first" });
tracker.markAudio(first);
tracker.close(first);
const second = tracker.open({ id: "second" });
tracker.markAudio(second);
const third = tracker.open({ id: "third" });
tracker.markAudio(third);
expect(tracker.consumeAudioContext()).toEqual({ id: "second" });
expect(tracker.consumeAudioContext()).toEqual({ id: "third" });
expect(tracker.consumeAudioContext()).toBeUndefined();
});
it("allows a zero turn limit", () => {
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>({ limit: 0 });
const turn = tracker.open({ id: "discarded" });
tracker.markAudio(turn);
expect(tracker.size()).toBe(0);
expect(turn.hasAudio).toBe(true);
expect(tracker.consumeAudioContext()).toBeUndefined();
});
it("consumes recently ignored contexts once before the ttl expires", () => {
let now = 1_000;
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>({
ignoredContextTtlMs: 500,
now: () => now,
});
tracker.rememberIgnoredContext({ id: "recent" });
now = 1_400;
expect(tracker.consumeIgnoredContext()).toEqual({ id: "recent" });
expect(tracker.consumeIgnoredContext()).toBeUndefined();
});
it("retains valid falsy ignored contexts", () => {
const numbers = createRealtimeVoiceTurnContextTracker<number>();
numbers.rememberIgnoredContext(0);
expect(numbers.consumeIgnoredContext()).toBe(0);
const text = createRealtimeVoiceTurnContextTracker<string>();
text.rememberIgnoredContext("");
expect(text.consumeIgnoredContext()).toBe("");
});
it("expires ignored contexts after the ttl", () => {
let now = 1_000;
const tracker = createRealtimeVoiceTurnContextTracker<{ id: string }>({
ignoredContextTtlMs: 500,
now: () => now,
});
tracker.rememberIgnoredContext({ id: "old" });
now = 1_501;
expect(tracker.consumeIgnoredContext()).toBeUndefined();
});
});