mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Add continuous globe rotation with direction and speed controls, viewport fit checks, camera ownership handoff, and voice refusal guards.
975 lines
22 KiB
JavaScript
975 lines
22 KiB
JavaScript
import { HUD_LAYOUTS } from '../hudLayoutPolicy.js';
|
|
|
|
// Canonical action arguments. Descriptive wording is supplied separately.
|
|
import {
|
|
VOICE_CONTEXT_LAYER_IDS,
|
|
VOICE_QUERY_LAYER_IDS,
|
|
VOICE_TOGGLE_LAYER_IDS,
|
|
} from './layerManifest.js';
|
|
|
|
// Layer enums come from the voice layer manifest so every shipped layer is
|
|
// reachable; the manifest test keeps it in step with the layer catalog.
|
|
const toggleLayerIds = () => [...VOICE_TOGGLE_LAYER_IDS];
|
|
|
|
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',
|
|
},
|
|
referent: {
|
|
type: 'integer',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
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: toggleLayerIds(),
|
|
},
|
|
enabled: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
required: ['layerId', 'enabled'],
|
|
},
|
|
},
|
|
{
|
|
name: 'show_data_layers_menu',
|
|
parameters: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
layerId: {
|
|
type: 'string',
|
|
enum: toggleLayerIds(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
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',
|
|
'weather-panel',
|
|
'recent-imagery-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', 'pointer'],
|
|
},
|
|
referent: {
|
|
type: 'integer',
|
|
},
|
|
layerId: {
|
|
type: 'string',
|
|
enum: [...VOICE_CONTEXT_LAYER_IDS],
|
|
},
|
|
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: [...HUD_LAYOUTS],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'set_cyber_sonar',
|
|
parameters: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
enabled: { type: 'boolean' },
|
|
rings: { type: 'integer', minimum: 3, maximum: 12 },
|
|
rangePct: { type: 'integer', minimum: 60, maximum: 120 },
|
|
intensityPct: { type: 'integer', minimum: 0, maximum: 100 },
|
|
opacityPct: { type: 'integer', minimum: 35, maximum: 100 },
|
|
sectorDeg: { type: 'integer', minimum: 8, maximum: 60 },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
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',
|
|
minLength: 1,
|
|
pattern: '\\S',
|
|
},
|
|
layerId: {
|
|
type: 'string',
|
|
},
|
|
referent: {
|
|
type: 'integer',
|
|
enum: [-1, 1, 2, 3, 4, 5],
|
|
},
|
|
},
|
|
anyOf: [
|
|
{ type: 'object', required: ['query'] },
|
|
{ type: 'object', required: ['referent'] },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
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', 'rotate_globe', '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: [...VOICE_QUERY_LAYER_IDS],
|
|
},
|
|
},
|
|
scope: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
kind: {
|
|
type: 'string',
|
|
enum: ['view', 'region', 'radius', 'anywhere', 'pointer'],
|
|
},
|
|
name: {
|
|
type: 'string',
|
|
},
|
|
km: {
|
|
type: 'number',
|
|
},
|
|
center: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
lat: {
|
|
type: 'number',
|
|
minimum: -90,
|
|
maximum: 90,
|
|
},
|
|
lon: {
|
|
type: 'number',
|
|
minimum: -180,
|
|
maximum: 180,
|
|
},
|
|
},
|
|
required: ['lat', 'lon'],
|
|
},
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'next_satellite_pass',
|
|
parameters: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
required: ['target'],
|
|
properties: {
|
|
target: { type: 'string', minLength: 1, maxLength: 120 },
|
|
latitude: { type: 'number', minimum: -90, maximum: 90 },
|
|
longitude: { type: 'number', minimum: -180, maximum: 180 },
|
|
minElevationDeg: { type: 'number', minimum: 5, maximum: 60 },
|
|
visibleOnly: { type: 'boolean' },
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
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) => {
|
|
const tool = {
|
|
type: 'function',
|
|
...describe(schema, descriptions[schema.name] || {}),
|
|
};
|
|
// Function-calling APIs reject anyOf/oneOf/allOf at the top level of
|
|
// parameters; the runner and benchmark validate those rules themselves.
|
|
for (const key of TOP_LEVEL_COMBINATORS) delete tool.parameters?.[key];
|
|
return tool;
|
|
});
|
|
}
|
|
|
|
const TOP_LEVEL_COMBINATORS = ['anyOf', 'oneOf', 'allOf'];
|
|
|
|
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;
|
|
}
|