mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Hosts serve panels from their own sites and may refuse other addresses; Codex refuses any address on the user's machine. The panel now never requests the app's server: it loads the app's panel build and data through panel_request, a tool only the panel may call, which requests the path from the app's server. One path in every host; only map providers load directly. npm run build:panel writes the panel build to dist/panel, served at /panel/: one app script, one stylesheet, Cesium's script, and Cesium's workers as one script Cesium runs from memory. Tools may now be marked app-only. The panel CORS rules are gone.
99 lines
3.5 KiB
JavaScript
99 lines
3.5 KiB
JavaScript
/**
|
|
* Which surfaces offer each tool. A tool is on every surface unless this
|
|
* table turns it off; edit an entry to turn a tool on or off for MCP, voice,
|
|
* or both. Applications can pass overrides when they compose a surface.
|
|
* Tools off a surface stay reachable from tools that combine others, such as
|
|
* situation_brief using get_weather.
|
|
*/
|
|
|
|
import { ToolError } from './catalog.js';
|
|
|
|
export const SURFACES = Object.freeze(['mcp', 'voice']);
|
|
|
|
const voiceOff = Object.freeze({ voice: false });
|
|
const mcpOff = Object.freeze({ mcp: false });
|
|
|
|
export const TOOL_SURFACES = Object.freeze({
|
|
// Voice runs inside the app, so a link to it adds nothing.
|
|
show_in_gods_eye_view: voiceOff,
|
|
// Only the God's Eye View panel calls this, to load the app.
|
|
panel_request: voiceOff,
|
|
// Voice has an app action of the same name.
|
|
next_satellite_pass: voiceOff,
|
|
// Voice receives text only, so tools that answer with an image are off.
|
|
get_weather_map: voiceOff,
|
|
get_recent_imagery: voiceOff,
|
|
get_cctv_snapshot: voiceOff,
|
|
// The app shows its HUD caption itself, and assistants write their own.
|
|
get_hud_caption: Object.freeze({ voice: false, mcp: false }),
|
|
// Voice answers these from the loaded layers with analyst_query.
|
|
aircraft_in_area: voiceOff,
|
|
vessels_in_area: voiceOff,
|
|
find_aircraft: voiceOff,
|
|
find_vessel: voiceOff,
|
|
get_earthquakes: voiceOff,
|
|
get_active_fires: voiceOff,
|
|
find_infrastructure: voiceOff,
|
|
satellites_overhead: voiceOff,
|
|
// MCP leads with what the globe shows; assistants already search places,
|
|
// plan routes and report weather, and these layers add little in a chat.
|
|
search_places: mcpOff,
|
|
places_nearby: mcpOff,
|
|
plan_route: mcpOff,
|
|
get_weather: mcpOff,
|
|
get_wind: mcpOff,
|
|
get_regional_brief: mcpOff,
|
|
find_radio_stations: mcpOff,
|
|
get_bike_share: mcpOff,
|
|
get_transit_vehicles: mcpOff,
|
|
});
|
|
|
|
/**
|
|
* The tools a surface offers, in their original order. `overrides` uses the
|
|
* table's shape and wins over it, such as `{ get_weather_map: { voice: true } }`.
|
|
* An override naming an unknown tool or surface throws, so a typo cannot
|
|
* silently do nothing.
|
|
*/
|
|
export function toolsForSurface(tools, surface, overrides = {}) {
|
|
if (!SURFACES.includes(surface))
|
|
throw new TypeError(`Unknown tool surface: ${surface}`);
|
|
const names = new Set(tools.map((tool) => tool.name));
|
|
for (const [name, entry] of Object.entries(overrides)) {
|
|
if (!names.has(name)) throw new TypeError(`Unknown tool: ${name}`);
|
|
for (const key of Object.keys(entry)) {
|
|
if (!SURFACES.includes(key))
|
|
throw new TypeError(`Unknown tool surface for ${name}: ${key}`);
|
|
}
|
|
}
|
|
return tools.filter(
|
|
(tool) =>
|
|
(overrides[tool.name]?.[surface] ??
|
|
TOOL_SURFACES[tool.name]?.[surface]) !== false,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A view of a composed catalog that lists and calls only the tools a surface
|
|
* offers. Tools that combine others still reach the whole catalog, so a
|
|
* composite such as military_awareness keeps its sections on every surface.
|
|
*/
|
|
export function catalogForSurface(catalog, surface, overrides = {}) {
|
|
const offered = new Set(
|
|
toolsForSurface(catalog.list(), surface, overrides).map(
|
|
(tool) => tool.name,
|
|
),
|
|
);
|
|
return Object.freeze({
|
|
list: () => catalog.list().filter((tool) => offered.has(tool.name)),
|
|
get: (name) => (offered.has(name) ? catalog.get(name) : undefined),
|
|
async call(name, args, options) {
|
|
if (!offered.has(name))
|
|
throw new ToolError(
|
|
'unsupported',
|
|
`No tool named ${name} is available`,
|
|
);
|
|
return catalog.call(name, args, options);
|
|
},
|
|
});
|
|
}
|