mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
An ESLint no-unused-vars scan over src, server, scripts, tools, build and pinokio, repeated until it is stable, reported these. Runtime modules keep every call and drop only the unread binding: - src/ui/cockpitInput.js: the unread result of enter()/exit(). - src/layers/launches/rendering.js: the unread entity returned by entities.add(). - src/layers/radio/rendering.js: the unread map key in the horizon scan, which now iterates the map's values. QA scripts and tools drop an unused helper, two unused in-page module imports, unused counters and destructured fields, and an unread wait result. Tests drop source files read into variables that no assertion uses, redundant StyleManager imports (the shell-source helper already imports it), and unused destructured fields. The one remaining report is the `_` loop variable in src/tools/schema.js, which counts iterations and is intentional.
259 lines
12 KiB
JavaScript
259 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { GevRealtimeController } from './realtimeController.js';
|
|
import { PROGRESS_MAX_OUTPUT_TOKENS } from './realtimeTurns.js';
|
|
|
|
function controllerFixture({ runner = async () => ({ ok: true }) } = {}) {
|
|
const sent = [];
|
|
const statuses = [];
|
|
const usage = [];
|
|
const controller = new GevRealtimeController({
|
|
ui: {
|
|
root: { dataset: {}, classList: { remove() {} }, querySelectorAll: () => [] },
|
|
status: { textContent: '' },
|
|
detail: { textContent: '', title: '' },
|
|
errorDetail: { textContent: '' },
|
|
},
|
|
runner,
|
|
debugSink: null,
|
|
});
|
|
const setStatus = controller.setStatus.bind(controller);
|
|
controller.setStatus = (status, detail) => {
|
|
statuses.push(status);
|
|
return setStatus(status, detail);
|
|
};
|
|
const recordUsage = controller.recordUsage.bind(controller);
|
|
controller.recordUsage = (value) => {
|
|
if (value) usage.push(value);
|
|
return recordUsage(value);
|
|
};
|
|
controller.dc = {
|
|
readyState: 'open',
|
|
send: (body) => sent.push(JSON.parse(body)),
|
|
close() {},
|
|
};
|
|
const event = (payload) =>
|
|
controller.handleRealtimeEvent({ data: JSON.stringify(payload) });
|
|
// Server errors carry their own event_id; the client's id is in error.event_id.
|
|
let serverSeq = 0;
|
|
const rejection = (clientEventId, code = 'invalid_request_error') =>
|
|
event({
|
|
type: 'error',
|
|
event_id: `event_server_${++serverSeq}`,
|
|
error: { type: 'invalid_request_error', code, message: 'rejected', event_id: clientEventId },
|
|
});
|
|
const progressLine = async (id = 'resp_p', line = 'Heading to Austin.') => {
|
|
assert.equal(controller._turns.speakProgressLine(line), true);
|
|
const request = sent.at(-1);
|
|
await event({ type: 'response.created', response: { id, metadata: { gev: 'progress' } } });
|
|
return request;
|
|
};
|
|
return { controller, sent, statuses, usage, event, rejection, progressLine };
|
|
}
|
|
|
|
const types = (sent) => sent.map((message) => message.type);
|
|
|
|
test('Space barge-in cancels the reply and clears audio; WebRTC truncates, so no manual truncate', async () => {
|
|
const { controller, sent, statuses, event, rejection } = controllerFixture();
|
|
await event({ type: 'response.created', response: { id: 'resp_1' } });
|
|
await event({ type: 'output_audio_buffer.started', response_id: 'resp_1' });
|
|
controller.pendingResponseInstructions = 'queued follow-up';
|
|
|
|
assert.equal(controller._turns.bargeIn(), true);
|
|
assert.deepEqual(types(sent), ['response.cancel', 'output_audio_buffer.clear']);
|
|
assert.equal(sent[0].response_id, 'resp_1');
|
|
assert.equal(controller.pendingResponseInstructions, null, 'no queued follow-up talks over the user');
|
|
assert.equal(controller.isSupersededResponse('resp_1'), true, 'late calls from the cut reply are refused');
|
|
|
|
// The target may already have finished. The rejection names the client id
|
|
// in error.event_id while the envelope has a different server id.
|
|
await rejection(sent[0].event_id, 'response_cancel_not_active');
|
|
await rejection(sent[1].event_id);
|
|
assert.equal(statuses.includes('error'), false);
|
|
});
|
|
|
|
test('an unrelated server error with a colliding top-level id is still fatal', async () => {
|
|
const { controller, sent, statuses, event } = controllerFixture();
|
|
await event({ type: 'response.created', response: { id: 'resp_1' } });
|
|
controller._turns.bargeIn();
|
|
await event({ type: 'error', event_id: sent[0].event_id, error: { code: 'server_error', message: 'boom' } });
|
|
assert.equal(statuses.includes('error'), true, 'only error.event_id correlates a client event');
|
|
});
|
|
|
|
test('a rejected progress line releases the waiting reply without an error state', async () => {
|
|
const { controller, sent, statuses, rejection } = controllerFixture();
|
|
controller._turns.speakProgressLine('Heading to Austin.');
|
|
controller.queueResponseCreate('Speak the say line.');
|
|
assert.equal(sent.length, 1, 'the follow-up waits behind the line');
|
|
await rejection(sent[0].event_id);
|
|
assert.equal(statuses.includes('error'), false);
|
|
assert.equal(sent.at(-1).type, 'response.create');
|
|
assert.equal(sent.at(-1).response.instructions, 'Speak the say line.');
|
|
});
|
|
|
|
test('progress requests are out of band, bounded, and carry the line only as data', async () => {
|
|
const { controller, sent } = controllerFixture();
|
|
const line = 'Finding Texas State Capitol on the map.';
|
|
assert.equal(controller._turns.speakProgressLine(line), true);
|
|
const { response } = sent[0];
|
|
assert.equal(response.conversation, 'none');
|
|
assert.equal(response.tool_choice, 'none');
|
|
assert.equal(response.max_output_tokens, PROGRESS_MAX_OUTPUT_TOKENS);
|
|
assert.ok(PROGRESS_MAX_OUTPUT_TOKENS <= 100);
|
|
assert.deepEqual(response.metadata, { gev: 'progress' });
|
|
assert.equal(response.input[0].content[0].text, line);
|
|
assert.equal(response.instructions.includes('Capitol'), false, 'labels never enter instructions');
|
|
assert.equal(controller._turns.speakProgressLine('Another line.'), false, 'one line at a time');
|
|
});
|
|
|
|
test('a cancelled progress response never clears a pending Radio handoff', async () => {
|
|
const { controller, sent, event, progressLine, usage } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
// Radio prepared its playback while the line was generating.
|
|
controller.pendingRadioPlaybackResult = { ok: true, radioPlaybackRequested: true };
|
|
controller.queueResponseCreate('Say "Turning on the radio."');
|
|
controller._turns.retractProgressLine();
|
|
await event({
|
|
type: 'response.done',
|
|
response: { id: 'resp_p', status: 'cancelled', metadata: { gev: 'progress' }, usage: { input_tokens: 5, output_tokens: 3 } },
|
|
});
|
|
assert.ok(controller.pendingRadioPlaybackResult, 'Radio stays pending');
|
|
assert.equal(sent.at(-1).type, 'response.create');
|
|
assert.match(sent.at(-1).response.instructions, /Turning on the radio/, 'the confirmation is not stranded');
|
|
assert.equal(usage.length, 1, 'progress usage is still billed');
|
|
});
|
|
|
|
test('an old progress completion leaves the active in-band response alone', async () => {
|
|
const { controller, event, progressLine } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
// Server VAD starts a real reply while the line is still generating.
|
|
await event({ type: 'response.created', response: { id: 'resp_real' } });
|
|
await event({ type: 'response.output_audio_transcript.done', response_id: 'resp_p', transcript: 'Heading to Austin.' });
|
|
await event({ type: 'response.done', response: { id: 'resp_p', status: 'completed', metadata: { gev: 'progress' } } });
|
|
assert.equal(controller.activeResponseId, 'resp_real');
|
|
assert.equal(controller.responseActive, true);
|
|
});
|
|
|
|
test('user speech stops an audible progress line: cancel and clear its audio', async () => {
|
|
const { sent, event, progressLine } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
await event({ type: 'output_audio_buffer.started', response_id: 'resp_p' });
|
|
sent.length = 0;
|
|
await event({ type: 'input_audio_buffer.speech_started' });
|
|
assert.deepEqual(types(sent), ['response.cancel', 'output_audio_buffer.clear']);
|
|
assert.equal(sent[0].response_id, 'resp_p');
|
|
});
|
|
|
|
test('a typed command stops an audible progress line too', async () => {
|
|
const { controller, sent, event, progressLine } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
await event({ type: 'output_audio_buffer.started', response_id: 'resp_p' });
|
|
await event({ type: 'response.done', response: { id: 'resp_p', status: 'completed', metadata: { gev: 'progress' } } });
|
|
sent.length = 0;
|
|
controller.sendTextCommand('go to Paris');
|
|
assert.equal(types(sent)[0], 'output_audio_buffer.clear', 'playback still owned after generation ended');
|
|
});
|
|
|
|
test('a finished tool lets a heard line finish, and playback ownership outlives response.done', async () => {
|
|
const { controller, sent, event, progressLine } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
await event({ type: 'output_audio_buffer.started', response_id: 'resp_p' });
|
|
controller._turns.retractProgressLine();
|
|
assert.equal(types(sent).includes('response.cancel'), false, 'audible lines are not cut');
|
|
controller.queueResponseCreate('Speak the say line.');
|
|
assert.equal(types(sent).filter((t) => t === 'response.create').length, 1, 'follow-up waits for generation');
|
|
await event({ type: 'response.done', response: { id: 'resp_p', status: 'completed', metadata: { gev: 'progress' } } });
|
|
assert.equal(types(sent).filter((t) => t === 'response.create').length, 2, 'follow-up after the line');
|
|
assert.equal(controller._turns.canNarrate(), false, 'still playing: no new line');
|
|
await event({ type: 'output_audio_buffer.stopped', response_id: 'resp_p' });
|
|
assert.equal(controller._turns.progressResponse, null);
|
|
});
|
|
|
|
test('an unheard line is cancelled when its tool finishes', async () => {
|
|
const { controller, sent, progressLine } = controllerFixture();
|
|
await progressLine('resp_p');
|
|
controller._turns.retractProgressLine();
|
|
assert.equal(sent.at(-1).type, 'response.cancel');
|
|
assert.equal(sent.at(-1).response_id, 'resp_p');
|
|
});
|
|
|
|
test('Radio handoff phases block barge-in and narration: prepared, reserved and starting playback', async () => {
|
|
const { controller, sent, event } = controllerFixture();
|
|
await event({ type: 'response.created', response: { id: 'resp_r' } });
|
|
await event({ type: 'response.done', response: { id: 'resp_r', status: 'completed' } });
|
|
await event({ type: 'output_audio_buffer.started', response_id: 'resp_r' });
|
|
const radio = controller._radio;
|
|
const phases = [
|
|
() => { radio.pendingRadioPlaybackResult = { ok: true }; },
|
|
() => { radio.radioVisibilityOffPending = true; },
|
|
() => { radio.radioHandoffInFlight = true; },
|
|
];
|
|
for (const enter of phases) {
|
|
radio.pendingRadioPlaybackResult = null;
|
|
radio.radioVisibilityOffPending = false;
|
|
radio.radioHandoffInFlight = false;
|
|
enter();
|
|
assert.equal(radio.mayVoiceClaimSpeaker(), false);
|
|
assert.equal(controller._turns.bargeIn(), false);
|
|
assert.equal(controller._turns.canNarrate(), false);
|
|
}
|
|
assert.deepEqual(sent, []);
|
|
});
|
|
|
|
test('caption transcription is metered with the served transcription model', async () => {
|
|
const { controller, event } = controllerFixture();
|
|
controller._cost.bindTranscriptionModel('gpt-4o-mini-transcribe');
|
|
const before = controller.costTracker.state().totalUsd;
|
|
await event({
|
|
type: 'conversation.item.input_audio_transcription.completed',
|
|
item_id: 'item_u',
|
|
transcript: 'take me to Tokyo',
|
|
usage: { type: 'tokens', input_tokens: 1000, input_token_details: { audio_tokens: 1000 }, output_tokens: 10 },
|
|
});
|
|
const after = controller.costTracker.state().totalUsd;
|
|
assert.ok(Math.abs(after - before - (1000 * 3 + 10 * 5) / 1_000_000) < 1e-12);
|
|
});
|
|
|
|
test('tools report start, progress and finish to narration; Radio stays silent', async () => {
|
|
const calls = [];
|
|
const { controller, event } = controllerFixture({
|
|
runner: async (name, args, options) => {
|
|
options.progress?.({ step: 'resolve', label: 'Zilker Park' });
|
|
return { ok: true, action: name };
|
|
},
|
|
});
|
|
controller._turns.narration = {
|
|
toolStarted: (id, info) => calls.push(['start', id, info.name, info.narrate, info.label]),
|
|
progress: (id, update) => calls.push(['progress', id, update.step]),
|
|
toolFinished: (id) => calls.push(['finish', id]),
|
|
preamble: () => calls.push(['preamble']),
|
|
userTurnEnded() {},
|
|
cancel() {},
|
|
audible() {},
|
|
quiet() {},
|
|
};
|
|
await event({ type: 'response.output_audio_transcript.delta', response_id: 'resp_t', delta: 'Finding ' });
|
|
await event({
|
|
type: 'response.function_call_arguments.done',
|
|
response_id: 'resp_t',
|
|
call_id: 'call_1',
|
|
name: 'annotate_map',
|
|
arguments: JSON.stringify({ annotations: [{ target: 'Zilker Park' }] }),
|
|
});
|
|
assert.deepEqual(calls, [
|
|
['preamble'],
|
|
['start', 'call_1', 'annotate_map', true, 'Zilker Park'],
|
|
['progress', 'call_1', 'resolve'],
|
|
['finish', 'call_1'],
|
|
]);
|
|
calls.length = 0;
|
|
await event({
|
|
type: 'response.function_call_arguments.done',
|
|
response_id: 'resp_r',
|
|
call_id: 'call_2',
|
|
name: 'control_radio',
|
|
arguments: JSON.stringify({ action: 'volume', volumePct: 20 }),
|
|
});
|
|
assert.equal(calls.find((call) => call[0] === 'start')?.[3], false);
|
|
});
|