refactor: separate voice action schemas from descriptions

This commit is contained in:
Sameh Khamis committed 2026-09-14 20:05:32 -07:00
1 parent 223cae609f
commit 55b240e3ab
11 files changed
+1623 -993

No files matched your search

+3 -3
View File
@@ -691,11 +691,11 @@ test('the voice TOOL SCHEMA matches the pinned release — the mission mapping i
});
test('every layer a mission drives is already in the shipped set_layer_visibility enum', () => {
const src = fs.readFileSync(new URL('../server/providers/openai/tools.js', import.meta.url), 'utf8');
const tool = src.slice(src.indexOf("name: 'set_layer_visibility'"), src.indexOf("name: 'show_data_layers_menu'"));
const tool = GEV_REALTIME_TOOLS.find(tool => tool.name === 'set_layer_visibility');
const allowedLayers = tool.parameters.properties.layerId.enum;
const missionLayerIds = Object.values(FIRST_RUN_MISSIONS).flatMap((mission) => mission.layerIds || []);
assert.ok(missionLayerIds.length > 0);
for (const layerId of missionLayerIds) {
assert.ok(tool.includes(`'${layerId}'`), `${layerId} must already be an allowed enum value`);
assert.ok(allowedLayers.includes(layerId), `${layerId} must already be an allowed enum value`);
}
});
+3 -10
View File
@@ -1,3 +1,4 @@
import { GEV_REALTIME_TOOLS } from '../server/providers/openai/tools.js';
import { readShellSource } from './testSupport/readShellSource.mjs';
import { expandApplicationHtml } from '../build/application-html.js';
import { readLayerSource } from './testSupport/readLayerSource.mjs';
@@ -18,18 +19,10 @@ const radio = ['playback', 'interaction'].map(name =>
).join('\n').replace(/layerState\.|parts\.\w+\./g, '');
const rocketLaunches = readLayerSource(new URL('./data/rocketLaunches.js', import.meta.url), 'utf8');
const realtime = readFileSync(new URL('./voice/realtimeController.js', import.meta.url), 'utf8');
const voice = ['tools', 'instructions'].map(name => readFileSync(new URL(`../server/providers/openai/${name}.js`, import.meta.url), 'utf8')).join('\n');
const voice = readFileSync(new URL('./voice/actionSchemas.js', import.meta.url), 'utf8') + '\n' + ['toolDescriptions', 'instructions'].map(name => readFileSync(new URL(`../server/providers/openai/${name}.js`, import.meta.url), 'utf8')).join('\n');
const css = readStylesheet(new URL('../style.css', import.meta.url));
/** Parse the Realtime tool array out of the Vite config as real data. */
function realtimeTools() {
const start = voice.indexOf('const GEV_REALTIME_TOOLS = [');
const end = voice.indexOf('\n];', start);
assert.ok(start >= 0 && end > start, 'Realtime tool schema block is missing');
const literal = voice.slice(start + 'const GEV_REALTIME_TOOLS = '.length, end + 2);
// The block is pure data; evaluating it beats regexing nested schemas.
return new Function(`return ${literal};`)();
}
function realtimeTools() { return GEV_REALTIME_TOOLS; }
test('Realtime schema exposes the authoritative 28-tool inventory', () => {
const tools = realtimeTools();
+944
View File
@@ -0,0 +1,944 @@
// Canonical action arguments. Descriptive wording is supplied separately.
const schemas = [
{
name: 'fly_to_location',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
locationId: {
type: 'string',
enum: [
'austin',
'sf',
'nyc',
'tokyo',
'london',
'paris',
'dubai',
'dc',
],
},
query: {
type: 'string',
},
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
viewMode: {
type: 'string',
enum: ['close', 'overview'],
},
rangeM: {
type: 'number',
minimum: 100,
maximum: 20000000,
},
waitForArrival: {
type: 'boolean',
},
},
},
},
{
name: 'select_nearest_aircraft',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
layerId: {
type: 'string',
enum: ['flights', 'military'],
},
locationId: {
type: 'string',
enum: [
'austin',
'sf',
'nyc',
'tokyo',
'london',
'paris',
'dubai',
'dc',
],
},
locationQuery: {
type: 'string',
maxLength: 160,
},
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
},
required: ['layerId'],
},
},
{
name: 'adjust_camera_zoom',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
direction: {
type: 'string',
enum: ['in', 'out'],
},
amount: {
type: 'string',
enum: ['little', 'medium', 'lot'],
},
},
required: ['direction', 'amount'],
},
},
{
name: 'zoom_to_globe',
parameters: {
type: 'object',
additionalProperties: false,
properties: {},
},
},
{
name: 'set_layer_visibility',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
layerId: {
type: 'string',
enum: [
'flights',
'military',
'earthquakes',
'satellites',
'rocket-launches',
'traffic',
'cctv',
'radio',
'bikeshare',
'ais-live-vessels',
'local-datacenters',
'local-dams',
'telegeography-submarine-cables',
'local-firms',
'alpr-cameras',
],
},
enabled: {
type: 'boolean',
},
},
required: ['layerId', 'enabled'],
},
},
{
name: 'show_data_layers_menu',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
layerId: {
type: 'string',
enum: [
'flights',
'military',
'earthquakes',
'satellites',
'traffic',
'cctv',
'radio',
'bikeshare',
'ais-live-vessels',
'local-datacenters',
'local-dams',
'telegeography-submarine-cables',
'local-firms',
'alpr-cameras',
],
},
},
},
},
{
name: 'set_panel_open',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
panelId: {
type: 'string',
enum: [
'data-panel',
'location-bar',
'control-panel',
'cctv-panel',
'radio-panel',
'scene-panel',
'pp-toggles',
'global-context-panel',
],
},
open: {
type: 'boolean',
},
},
required: ['panelId', 'open'],
},
},
{
name: 'set_context_mode',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
mode: {
type: 'string',
enum: ['off', 'contacts', 'flights', 'space-missions', 'missions'],
},
},
required: ['mode'],
},
},
{
name: 'control_cockpit',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
action: {
type: 'string',
enum: ['enter', 'exit', 'previous', 'next', 'prev', 'status'],
},
targetLayer: {
type: 'string',
enum: [
'flights',
'military',
'ais-live-vessels',
'military-installations',
],
},
aircraftClass: {
type: 'string',
},
},
required: ['action'],
},
},
{
name: 'set_visual_style',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
style: {
type: 'string',
enum: [
'normal',
'retro',
'surveillance',
'thermal',
'anime',
'noir',
'snow',
],
},
},
required: ['style'],
},
},
{
name: 'get_entity_context',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
scope: {
type: 'string',
enum: ['auto', 'selected', 'in_view'],
},
layerId: {
type: 'string',
enum: [
'local-datacenters',
'local-dams',
'telegeography-submarine-cables',
'local-firms',
],
},
limit: {
type: 'number',
minimum: 1,
maximum: 12,
},
},
},
},
{
name: 'get_current_view_state',
parameters: {
type: 'object',
additionalProperties: false,
properties: {},
},
},
{
name: 'set_hud',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
visible: {
type: 'string',
enum: ['on', 'off', 'auto'],
},
layout: {
type: 'string',
enum: ['tactical', 'operator', 'minimal'],
},
},
},
},
{
name: 'set_detection',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
enabled: {
type: 'boolean',
},
mode: {
type: 'string',
enum: ['sparse', 'balanced', 'dense'],
},
densityPct: {
type: 'number',
},
allocationStrategy: {
type: 'string',
enum: ['elastic', 'weighted'],
},
},
},
},
{
name: 'set_map_stack',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
stack: {
type: 'string',
enum: [
'photoreal',
'bing-aerial',
'bing-labels',
'esri-imagery',
'osm',
],
},
},
required: ['stack'],
},
},
{
name: 'set_post_processing',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
bloom: {
type: 'object',
additionalProperties: false,
properties: {
enabled: {
type: 'boolean',
},
intensityPct: {
type: 'number',
},
},
},
sharpen: {
type: 'object',
additionalProperties: false,
properties: {
enabled: {
type: 'boolean',
},
intensityPct: {
type: 'number',
},
},
},
},
},
},
{
name: 'control_scene',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
action: {
type: 'string',
enum: ['list', 'play', 'stop', 'next', 'status'],
},
sceneId: {
type: 'string',
},
},
required: ['action'],
},
},
{
name: 'control_cctv',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
action: {
type: 'string',
enum: [
'enable',
'disable',
'select',
'next',
'prev',
'nearest',
'focus',
'coverage',
'viewshed',
'adjust',
'projection',
'autohop',
],
},
cameraQuery: {
type: 'string',
},
enabled: {
type: 'boolean',
},
},
required: ['action'],
},
},
{
name: 'control_radio',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
action: {
type: 'string',
enum: [
'enable',
'disable',
'play',
'resume',
'pause',
'stop',
'next',
'previous',
'volume',
'select',
'status',
],
},
volumePct: {
type: 'number',
minimum: 0,
maximum: 100,
},
category: {
type: 'string',
enum: [
'all',
'news',
'talk',
'weather',
'public-safety',
'aviation-marine',
'traffic-transit',
'music',
],
},
locationId: {
type: 'string',
enum: [
'austin',
'sf',
'nyc',
'tokyo',
'london',
'paris',
'dubai',
'dc',
],
},
locationQuery: {
type: 'string',
maxLength: 120,
},
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
country: {
type: 'string',
maxLength: 80,
},
stationQuery: {
type: 'string',
maxLength: 120,
},
},
required: ['action'],
},
},
{
name: 'track_entity',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
query: {
type: 'string',
},
layerId: {
type: 'string',
},
},
required: ['query'],
},
},
{
name: 'stop_tracking',
parameters: {
type: 'object',
additionalProperties: false,
properties: {},
},
},
{
name: 'frame_overhead',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
target: {
type: 'string',
enum: ['flights', 'military', 'satellites', 'vessels'],
},
radiusKm: {
type: 'number',
},
},
required: ['target'],
},
},
{
name: 'annotate_map',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
annotations: {
type: 'array',
minItems: 1,
maxItems: 24,
items: {
type: 'object',
additionalProperties: false,
properties: {
type: {
type: 'string',
enum: ['pin', 'highlight', 'area', 'arrow', 'route', 'label'],
},
target: {
type: 'string',
maxLength: 200,
},
points: {
type: 'array',
minItems: 2,
maxItems: 12,
items: {
type: 'object',
additionalProperties: false,
properties: {
target: {
type: 'string',
maxLength: 200,
},
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
screenX: {
type: 'number',
minimum: 0,
maximum: 1,
},
screenY: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
},
},
mode: {
type: 'string',
enum: ['walking', 'driving', 'cycling'],
},
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
toTarget: {
type: 'string',
maxLength: 200,
},
toLatitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
toLongitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
label: {
type: 'string',
maxLength: 120,
},
color: {
type: 'string',
enum: ['primary', 'amber', 'cyan', 'green', 'red'],
},
footprint: {
type: 'boolean',
},
intent: {
type: 'string',
enum: ['the_thing', 'around_the_thing'],
},
entityKind: {
type: 'string',
enum: [
'building',
'compound',
'district',
'street',
'point_feature',
],
},
screenX: {
type: 'number',
minimum: 0,
maximum: 1,
},
screenY: {
type: 'number',
minimum: 0,
maximum: 1,
},
toScreenX: {
type: 'number',
minimum: 0,
maximum: 1,
},
toScreenY: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
required: ['type'],
},
},
flyTo: {
type: 'boolean',
},
persist: {
type: 'boolean',
},
},
required: ['annotations'],
},
},
{
name: 'clear_annotations',
parameters: {
type: 'object',
additionalProperties: false,
properties: {},
},
},
{
name: 'move_camera',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
motion: {
type: 'string',
enum: ['orbit', 'pan', 'tilt', 'rotate', 'stop'],
},
direction: {
type: 'string',
enum: ['left', 'right', 'up', 'down'],
},
speed: {
type: 'string',
enum: ['slow', 'normal', 'fast'],
},
mode: {
type: 'string',
enum: ['once', 'continuous'],
},
},
required: ['motion'],
},
},
{
name: 'fly_route',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
label: {
type: 'string',
},
speed: {
type: 'string',
enum: ['slow', 'normal', 'fast'],
},
},
},
},
{
name: 'analyst_query',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
layers: {
type: 'array',
items: {
type: 'string',
enum: [
'flights',
'military',
'ais-live-vessels',
'local-firms',
'earthquakes',
],
},
},
scope: {
type: 'object',
additionalProperties: false,
properties: {
kind: {
type: 'string',
enum: ['view', 'region', 'radius', 'anywhere'],
},
name: {
type: 'string',
},
km: {
type: 'number',
},
center: {
type: 'object',
additionalProperties: false,
properties: {
lat: {
type: 'number',
},
lon: {
type: 'number',
},
},
},
},
},
filters: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
properties: {
field: {
type: 'string',
},
op: {
type: 'string',
enum: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq', 'contains'],
},
value: {},
},
required: ['field', 'op', 'value'],
},
},
sortBy: {
type: 'string',
},
sortDir: {
type: 'string',
enum: ['asc', 'desc'],
},
limit: {
type: 'number',
},
followUp: {
type: 'boolean',
},
},
},
},
{
name: 'next_iss_pass',
parameters: {
type: 'object',
additionalProperties: false,
properties: {
latitude: {
type: 'number',
minimum: -90,
maximum: 90,
},
longitude: {
type: 'number',
minimum: -180,
maximum: 180,
},
minElevationDeg: {
type: 'number',
minimum: 5,
maximum: 60,
},
},
},
},
];
function freeze(value) {
if (value && typeof value === 'object') {
Object.values(value).forEach(freeze);
Object.freeze(value);
}
return value;
}
/** Immutable action names and argument schemas, without model-facing wording. */
export const GEV_ACTION_SCHEMAS = freeze(schemas);
/** Build independent function-tool records with description-only metadata. */
export function createActionTools(descriptions = {}) {
for (const name of Object.keys(descriptions)) {
if (!schemas.some((schema) => schema.name === name))
throw new TypeError('Unknown action description: ' + name);
}
return schemas.map((schema) => ({
type: 'function',
...describe(schema, descriptions[schema.name] || {}),
}));
}
function describe(schema, metadata, allowDescription = true) {
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))
throw new TypeError('Descriptions must be an object');
const result = structuredClone(schema);
for (const [key, value] of Object.entries(metadata)) {
if (key === '$position' && allowDescription) continue;
if (key === 'description' && allowDescription) {
if (typeof value !== 'string')
throw new TypeError('Description must be text');
Object.defineProperty(result, key, {
value,
enumerable: true,
writable: true,
configurable: true,
});
} else {
if (
!Object.hasOwn(schema, key) ||
!schema[key] ||
typeof schema[key] !== 'object'
)
throw new TypeError(
'Descriptions cannot change action arguments: ' + key,
);
Object.defineProperty(result, key, {
value: describe(
schema[key],
value,
!Array.isArray(schema[key]) &&
(!allowDescription || key !== 'properties'),
),
enumerable: true,
writable: true,
configurable: true,
});
}
}
if (
Object.hasOwn(metadata, '$position') &&
(!Object.hasOwn(metadata, 'description') ||
!Number.isInteger(metadata.$position) ||
metadata.$position < 0 ||
metadata.$position > Object.keys(schema).length)
)
throw new TypeError('Invalid description position');
if (allowDescription && Object.hasOwn(metadata, 'description')) {
const entries = Object.entries(result).filter(
([key]) => key !== 'description',
);
entries.splice(metadata.$position ?? entries.length, 0, [
'description',
metadata.description,
]);
return Object.fromEntries(entries);
}
return result;
}
+78
View File
@@ -0,0 +1,78 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { GEV_ACTION_SCHEMAS, createActionTools } from './actionSchemas.js';
import { GEV_REALTIME_TOOLS } from '../../server/providers/openai/tools.js';
const stable = (value) =>
Array.isArray(value)
? value.map(stable)
: value && typeof value === 'object'
? Object.fromEntries(
Object.entries(value)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, child]) => [key, stable(child)]),
)
: value;
test('the complete Realtime tool payload retains its pre-extraction contract and wording', () => {
const digest = createHash('sha256')
.update(JSON.stringify(stable(GEV_REALTIME_TOOLS)))
.digest('hex');
assert.equal(
digest,
'956381c3456d3644ed7c9cda72910dc68a34d9191e0b3e414ee200c348245214',
);
});
test('descriptions customize wording without changing immutable shared arguments', () => {
const descriptions = {
fly_to_location: {
description: 'Navigate',
parameters: { properties: { query: { description: 'A place' } } },
},
};
const tools = createActionTools(descriptions);
const tool = tools.find((tool) => tool.name === 'fly_to_location');
assert.equal(tool.description, 'Navigate');
assert.equal(tool.parameters.properties.query.description, 'A place');
assert.equal(tool.parameters.properties.query.type, 'string');
tool.parameters.properties.query.type = 'number';
assert.equal(
createActionTools()[0].parameters.properties.query.type,
'string',
);
assert.throws(() => {
GEV_ACTION_SCHEMAS[0].parameters.properties.query.type = 'number';
}, TypeError);
assert.equal(
JSON.stringify(GEV_ACTION_SCHEMAS).includes('"description"'),
false,
);
});
test('metadata cannot add tools, fields, types or enum values', () => {
for (const descriptions of [
{ execute_shell: { description: 'not an action' } },
{
fly_to_location: {
parameters: { properties: { description: 'new field' } },
},
},
{ fly_to_location: { $position: -1, description: 'invalid position' } },
{ fly_to_location: { name: 'other' } },
{
fly_to_location: {
parameters: { properties: { arbitrary: { description: 'new field' } } },
},
},
{
fly_to_location: {
parameters: { properties: { query: { type: 'number' } } },
},
},
{ fly_to_location: { parameters: { required: { 0: 'another' } } } },
{ fly_to_location: { description: { nested: 'invalid' } } },
])
assert.throws(() => createActionTools(descriptions), TypeError);
});