mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor(voice): isolate gemini live provider core
This commit is contained in:
commit
10916227cb
655 files changed
+45946
-26483
No files matched your search
@@ -0,0 +1,285 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
nativeIntegrationFixture,
|
||||
assertSuccessfulZoom,
|
||||
} from '../testSupport/geminiIntegrationFixture.mjs';
|
||||
|
||||
const options = { concurrency: false, timeout: 5000 };
|
||||
const OLD_PCM = 'BAAFAA==';
|
||||
const TEXT = 'Zoom in for the owned typed successor';
|
||||
|
||||
function exposeCapture(h) {
|
||||
const createAudio = h.controller.createAudio;
|
||||
h.controller.createAudio = (callbacks) => {
|
||||
const audio = createAudio(callbacks);
|
||||
audio.captureCallbacks = callbacks;
|
||||
return audio;
|
||||
};
|
||||
}
|
||||
|
||||
async function startManual(h) {
|
||||
await h.session.start({ pushToTalk: true });
|
||||
assert.equal(h.session.state, 'listening');
|
||||
assert.equal(h.controller.input.pushToTalkMode, true);
|
||||
assert.equal(Boolean(h.controller.input.pushToTalkKeyHeld), false);
|
||||
return h.sockets.at(-1);
|
||||
}
|
||||
|
||||
// Explicit fake worklet callbacks retain the native physical-capture path;
|
||||
// this helper does not populate controller queues or ownership fields itself.
|
||||
function speakAndRelease(h, data) {
|
||||
const c = h.controller;
|
||||
const audio = h.audios.at(-1);
|
||||
c.input.pushToTalkKeyHeld = true;
|
||||
c.input.setMicrophoneEnabled(true);
|
||||
const metadata = audio.metadata;
|
||||
assert.ok(Number.isSafeInteger(metadata?.captureId));
|
||||
audio.captureCallbacks.onAudioStart(metadata);
|
||||
audio.captureCallbacks.onAudio(
|
||||
{ data, mimeType: 'audio/pcm;rate=16000' },
|
||||
metadata,
|
||||
);
|
||||
c.input.pushToTalkKeyHeld = false;
|
||||
c.input.setMicrophoneEnabled(false);
|
||||
audio.captureCallbacks.onAudioEnd(metadata);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
async function prepareDeferredPtt(h) {
|
||||
exposeCapture(h);
|
||||
const socket = await startManual(h);
|
||||
speakAndRelease(h, 'AAABAA=='); // A is submitted and awaits its reply.
|
||||
speakAndRelease(h, 'AgADAA=='); // B is submitted, retiring A.
|
||||
const wireBeforeC = socket.sent.length;
|
||||
const metadata = speakAndRelease(h, OLD_PCM); // C waits behind A's boundary.
|
||||
const owner = h.controller.deferredInput;
|
||||
assert.equal(owner?.kind, 'push-to-talk');
|
||||
assert.equal(owner.captureId, metadata.captureId);
|
||||
assert.equal(
|
||||
socket.sent.length,
|
||||
wireBeforeC,
|
||||
'C must still be wholly deferred',
|
||||
);
|
||||
assert.deepEqual(
|
||||
owner.messages.map((message) =>
|
||||
message.realtimeInput?.activityStart
|
||||
? 'start'
|
||||
: message.realtimeInput?.audio
|
||||
? 'audio'
|
||||
: message.realtimeInput?.activityEnd
|
||||
? 'end'
|
||||
: 'other',
|
||||
),
|
||||
['start', 'audio', 'end'],
|
||||
);
|
||||
return { socket, owner, metadata };
|
||||
}
|
||||
|
||||
async function assertHealthySuccessor(h, socket, callId) {
|
||||
const beforeHeight = h.state.height;
|
||||
await h.tool(callId, socket);
|
||||
assertSuccessfulZoom(h, socket, callId, beforeHeight);
|
||||
assert.equal(h.controller.pendingTools, 0);
|
||||
h.complete(socket); // Native post-tool response boundary, with no output PCM.
|
||||
assert.equal(h.controller.narration.state().tools, 0);
|
||||
assert.equal(h.session.state, 'listening');
|
||||
}
|
||||
|
||||
for (const mode of ['typed replacement', 'Stop', 'Stop+restart']) {
|
||||
test(
|
||||
`F04-5 deferred PTT flush loses its owner on ${mode}`,
|
||||
options,
|
||||
async (t) => {
|
||||
const h = nativeIntegrationFixture(t);
|
||||
const { socket: oldSocket, owner } = await prepareDeferredPtt(h);
|
||||
const oldAudio = h.audios.at(-1);
|
||||
let observed = 0;
|
||||
let sentAtObserverReturn;
|
||||
let restart;
|
||||
let stateAtObserverReturn;
|
||||
const unsubscribe = h.session.subscribe((event) => {
|
||||
if (
|
||||
event.type !== 'transcript' ||
|
||||
event.role !== 'user' ||
|
||||
event.text !== '' ||
|
||||
observed
|
||||
)
|
||||
return;
|
||||
observed++;
|
||||
if (mode === 'typed replacement') {
|
||||
assert.equal(h.session.sendText(TEXT), true);
|
||||
} else {
|
||||
h.session.stop();
|
||||
if (mode === 'Stop+restart')
|
||||
restart = h.session.start({ pushToTalk: true });
|
||||
}
|
||||
sentAtObserverReturn = oldSocket.sent.length;
|
||||
stateAtObserverReturn = {
|
||||
epoch: h.controller.epoch,
|
||||
turn: h.controller.turn,
|
||||
replyPending: h.controller.replyPending,
|
||||
forwardedActivityOpen: h.controller.forwardedActivityOpen,
|
||||
};
|
||||
});
|
||||
t.after(unsubscribe);
|
||||
|
||||
h.complete(oldSocket); // A's boundary starts flushing C. C's start emits.
|
||||
assert.equal(
|
||||
observed,
|
||||
1,
|
||||
'must enter the actual flush/onset observer boundary',
|
||||
);
|
||||
assert.ok(
|
||||
stateAtObserverReturn,
|
||||
'observer must complete its replacement action',
|
||||
);
|
||||
assert.deepEqual(
|
||||
oldSocket.sent.slice(sentAtObserverReturn),
|
||||
[],
|
||||
'retired C must send neither queued PCM nor an extra end after the observer returns',
|
||||
);
|
||||
assert.deepEqual(
|
||||
{
|
||||
epoch: h.controller.epoch,
|
||||
turn: h.controller.turn,
|
||||
replyPending: h.controller.replyPending,
|
||||
forwardedActivityOpen: h.controller.forwardedActivityOpen,
|
||||
},
|
||||
stateAtObserverReturn,
|
||||
'old flush must not write successor state',
|
||||
);
|
||||
assert.equal(h.controller.deferredInput, null);
|
||||
assert.equal(
|
||||
h.controller.typedIntent?.text ?? null,
|
||||
mode === 'typed replacement' ? TEXT : null,
|
||||
);
|
||||
|
||||
let successorSocket;
|
||||
if (mode === 'typed replacement') {
|
||||
assert.equal(h.controller.epoch, owner.epoch);
|
||||
assert.notEqual(h.controller.turn, owner.turn);
|
||||
assert.equal(
|
||||
oldSocket.sent.some(
|
||||
(message) => message.realtimeInput?.audio?.data === OLD_PCM,
|
||||
),
|
||||
false,
|
||||
);
|
||||
const typedIndex = oldSocket.sent.findIndex((message) =>
|
||||
message.clientContent?.turns?.some((turn) =>
|
||||
turn.parts?.some((part) => part.text === TEXT),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
typedIndex >= 0,
|
||||
'owned replacement must actually reach transport',
|
||||
);
|
||||
// The replacement legitimately closes the already-forwarded C start once.
|
||||
assert.ok(oldSocket.sent[typedIndex - 1]?.realtimeInput?.activityEnd);
|
||||
h.complete(oldSocket); // B's remaining retired boundary, deliberately separate.
|
||||
assert.equal(h.controller.retiredReplyPending, false);
|
||||
assert.equal(h.controller.replyPending, true);
|
||||
successorSocket = oldSocket;
|
||||
} else {
|
||||
assert.equal(oldAudio.closed, true);
|
||||
assert.equal(oldSocket.closed, true);
|
||||
if (!restart) restart = h.session.start({ pushToTalk: true });
|
||||
await restart;
|
||||
successorSocket = h.sockets.at(-1);
|
||||
assert.notEqual(successorSocket, oldSocket);
|
||||
assert.equal(
|
||||
successorSocket.sent.some((message) => message.realtimeInput?.audio),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
successorSocket.sent.some(
|
||||
(message) => message.realtimeInput?.activityEnd,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(h.session.sendText(TEXT), true);
|
||||
}
|
||||
await assertHealthySuccessor(
|
||||
h,
|
||||
successorSocket,
|
||||
`healthy-after-deferred-${mode}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test(
|
||||
'F04-5 deferred typed onSent cannot resurrect replyPending after Stop+restart',
|
||||
options,
|
||||
async (t) => {
|
||||
const h = nativeIntegrationFixture(t);
|
||||
const oldSocket = await h.start();
|
||||
assert.equal(h.session.sendText('A'), true);
|
||||
assert.equal(h.session.sendText('B'), true);
|
||||
assert.equal(h.session.sendText('C deferred text'), true);
|
||||
assert.equal(h.controller.deferredInput?.kind, 'user-text');
|
||||
let restart;
|
||||
let observed = 0;
|
||||
const unsubscribe = h.session.subscribe((event) => {
|
||||
if (
|
||||
event.type !== 'transcript' ||
|
||||
event.role !== 'user' ||
|
||||
event.text !== 'C deferred text'
|
||||
)
|
||||
return;
|
||||
observed++;
|
||||
h.session.stop();
|
||||
restart = h.session.start();
|
||||
});
|
||||
t.after(unsubscribe);
|
||||
h.complete(oldSocket);
|
||||
assert.equal(observed, 1);
|
||||
assert.equal(
|
||||
h.controller.replyPending,
|
||||
false,
|
||||
'old onSent wrapper must not resurrect a stopped reply',
|
||||
);
|
||||
await restart;
|
||||
assert.equal(h.controller.replyPending, false);
|
||||
const socket = h.sockets.at(-1);
|
||||
assert.equal(
|
||||
socket.sent.length,
|
||||
1,
|
||||
'replacement connection should contain setup only',
|
||||
);
|
||||
assert.equal(h.session.sendText(TEXT), true);
|
||||
await assertHealthySuccessor(h, socket, 'healthy-after-deferred-text');
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'F04-5 ordinary deferred PTT flush preserves start, PCM and end exactly once',
|
||||
options,
|
||||
async (t) => {
|
||||
const h = nativeIntegrationFixture(t);
|
||||
const { socket } = await prepareDeferredPtt(h);
|
||||
const before = socket.sent.length;
|
||||
h.complete(socket);
|
||||
assert.deepEqual(
|
||||
socket.sent
|
||||
.slice(before)
|
||||
.map((message) =>
|
||||
message.realtimeInput?.activityStart
|
||||
? 'start'
|
||||
: message.realtimeInput?.audio?.data === OLD_PCM
|
||||
? 'audio'
|
||||
: message.realtimeInput?.activityEnd
|
||||
? 'end'
|
||||
: 'other',
|
||||
),
|
||||
['start', 'audio', 'end'],
|
||||
);
|
||||
assert.equal(h.controller.deferredInput, null);
|
||||
assert.equal(h.controller.replyPending, true);
|
||||
assert.equal(h.controller.retiredReplyPending, true);
|
||||
h.complete(socket); // B's boundary; C remains the owned submitted input.
|
||||
assert.equal(h.controller.retiredReplyPending, false);
|
||||
assert.equal(h.controller.replyPending, true);
|
||||
await assertHealthySuccessor(h, socket, 'healthy-ordinary-deferred');
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user