Runtime Type Declarations
Knowhere runtime globals, as actually registered by the sandboxed VM (see runtime/pool.go). This is the authoritative reference for the 'runtime' MCP tool — knowhere://docs/runtime is the prose API guide and knowhere://docs/recipes has worked scripts (both also served by the 'docs' tool, slugs 'runtime' and 'recipes').
Note: this is hand-maintained; the shape test suite
(services/runtime_shape_test.go) and deno check examples/ are what keep
it honest against the Go source.
Globals
declare const assert: Assert;
declare const colors: Colors;
declare const geo: Geo;
declare const query: Query;
declare const tags: Tags;
declare const budget: Budget;
declare const address: Address;
declare const routing: Routing;
Properties
interface Properties {
[key: string]: string | number | boolean;
}
Feature
// The live object behind every asFeature() is paulmach/orb's
// geojson.Feature crossing the Go/JS boundary, so it carries more than the
// three GeoJSON keys.
interface Feature {
type: string; // "Feature"
properties: FeatureProperties;
geometry: Geometry;
id: unknown; // null unless the source feature carried an id
bbox: number[] | null; // [minLon, minLat, maxLon, maxLat] when present
point(): OrbPoint; // center of the geometry's bounding box
// marshalJSON / marshalBSON / unmarshalJSON / unmarshalBSON also cross
// over (Go (de)serialisers trafficking in raw byte arrays). Of no use
// from JS — return the feature itself and let the server serialise it.
}
FeatureProperties
// A feature's properties: the map handed to asFeature(), plus the result's
// tags when appendTags was true. Read keys directly.
interface FeatureProperties {
[key: string]: unknown;
// Go helper methods that also cross over. AVOID THEM: each panics when
// the key is absent and no default is given, and the numeric ones panic
// on any number that entered through JS (boxed as a type they refuse).
// The panic is NOT a JS exception — try/catch cannot intercept it, the
// script just dies. properties["key"] answers the same question safely.
mustBool(key: string, def?: boolean): boolean;
mustInt(key: string, def?: number): number;
mustFloat64(key: string, def?: number): number;
mustString(key: string, def?: string): string;
clone(): FeatureProperties;
}
Geometry
// A raw orb geometry (feature.geometry, bounds.union()). Also array-like,
// holding coordinates in GeoJSON order: a Point is [lon, lat], a
// LineString an array of those, a Polygon an array of rings, a
// MultiPolygon an array of polygons.
interface Geometry {
geoJSONType(): string; // "Point" | "LineString" | "Polygon" | "MultiPolygon" | ...
dimensions(): number; // 0 point, 1 line, 2 area
bound(): OrbBound;
}
OrbPoint
// A raw orb.Point: array-like [lon, lat] (GeoJSON order) with helper
// methods. Not the wrapper Point above — no asFeature()/asBound().
interface OrbPoint {
0: number; // lon
1: number; // lat
lat(): number;
lon(): number;
}
OrbBound
// The raw orb.Bound behind geometry.bound(). NOT the wrapper Bound above:
// no asFeature()/asBB(), extend() takes a point not meters, and pad() is
// in DEGREES. min/max are fields here, not methods. Point arguments accept
// a plain [lon, lat] array or a wrapper Point.
interface OrbBound {
min: OrbPoint;
max: OrbPoint;
center(): OrbPoint;
contains(point: OrbPoint | [number, number] | Point): boolean;
intersects(other: OrbBound): boolean;
extend(point: OrbPoint | [number, number] | Point): OrbBound;
union(other: OrbBound): OrbBound;
pad(degrees: number): OrbBound;
left(): number; // minLon
right(): number; // maxLon
top(): number; // maxLat
bottom(): number; // minLat
isEmpty(): boolean;
toPolygon(): Geometry;
geoJSONType(): string;
}
Assert
interface Assert {
geoJSON(payload: unknown): void;
eq(value: boolean, message: string): void;
stab(msg: string): void;
}
Colors
interface Colors {
pick(index: number): string;
}
Bound
interface Bound {
extend(distanceMeters: number): Bound;
center(): Point;
intersects(other: Locatable): boolean; // throws on a non-location argument
contains(point: Locatable | [number, number]): boolean; // raw array is [lon, lat]; throws on a non-location
min(): [number, number]; // [minLon, minLat]
max(): [number, number]; // [maxLon, maxLat]
left(): number; // minLon
right(): number; // maxLon
top(): number; // maxLat
bottom(): number; // minLat
asFeature(properties?: Properties, appendTags?: boolean): Feature;
asBB(): string; // "minLon,minLat,maxLon,maxLat" — usable in (bb=...)
}
BoundArray
interface BoundArray extends Array<Bound> {
asFeature(properties?: Properties, appendTags?: boolean): Feature;
asBound(): Bound; // union bounding box
union(): Geometry; // true polygon union (a MultiPolygon, not just a bbox)
}
Point
// Also array-like: p[0] is LON, p[1] is LAT (GeoJSON order — the opposite
// of asPoint's argument order). Prefer the methods.
interface Point {
asFeature(properties?: Properties, appendTags?: boolean): Feature;
asBound(): Bound;
lat(): number;
lon(): number;
}
Area
interface Area {
name: string;
fullName: string;
minLat: number;
minLon: number;
maxLat: number;
maxLon: number;
asBound(): Bound;
}
Locatable
// Anything location-shaped. Every geometry entry point accepts any of
// these and THROWS on everything else — never a silent (0, 0).
type Locatable = Result | Point | Bound | Area | { lat: number; lon: number };
ResultCopy
// A plain-object copy of a Result ({...r}), recognized by carrying the
// bounding box. The read-only refusal on a Result recommends making one;
// asResults accepts it back.
type ResultCopy = {
min_lat: number;
min_lon: number;
max_lat: number;
max_lon: number;
};
Geo
interface Geo {
asPoint(lat: number, lon: number): Point; // throws if lat/lon out of range
point(coords: { lat: number; lon: number }): Point; // named keys — order can't be swapped
bound(box: { minLat: number; minLon: number; maxLat: number; maxLon: number }): Bound;
asResults(...results: (Result | ResultCopy)[]): ResultArray;
asBounds(...bounds: Locatable[]): BoundArray; // a point collapses to a degenerate box
distance(a: Locatable, b: Locatable): number; // meters, between centers
}
Result
// Reading any property NOT declared here throws UNKNOWN_PROPERTY (with the
// full roster and a did-you-mean) instead of returning undefined — a typo
// fails at the line that made it. Feature-test with ("x" in r); tag access
// (r.tags.anything) is ordinary and stays probe-free. Results are read-only.
interface Result {
asFeature(properties?: Properties, appendTags?: boolean): Feature;
name(): string; // method, not a property — reads the "name" tag if present
id: number;
osm_id: number;
osm_type: number; // 1=node, 2=way, 3=relation
lat: number; // bbox centroid — same value the search tool reports
lon: number;
min_lat: number;
min_lon: number;
max_lat: number;
max_lon: number;
tags: { [key: string]: unknown };
asBound(): Bound;
asPoint(): Point;
}
ResultArray
interface ResultArray extends Array<Result> {
tagCount(): { [key: string]: number };
}
Query
interface Query {
// Both stop at a row cap (50,000 by default) and THROW TOO_MANY_RESULTS
// rather than returning a shortened array — a truncated set reads exactly
// like a complete one. Use count() when you only need how many.
union(...queries: string[]): ResultArray;
execute(queryString: string): ResultArray; // sync — no await
count(queryString: string): number; // COUNT(*), no rows materialised
areas(): Area[];
// area defaults to the address's state, snake_cased, if omitted.
fromAddress(fullAddress: string, area?: string): ResultArray;
}
Tags
interface Tags {
// the tag_keys / tag_values tools, in-runtime; both ranked by frequency.
keys(area: string): { key: string; count: number }[];
values(area: string, key: string): { value: string; count: number }[];
}
Routing
interface Routing {
// travel modes this area has a graph for, e.g. ["car", "foot"].
// Empty means the area carries no routing at all.
profiles(area: string): string[];
// travel seconds, or null when unreachable. Cheaper than route():
// no shortcut expansion, no geometry.
time(area: string, from: PointLike, to: PointLike, options?: RouteOptions): number | null;
// one origin against many destinations, sharing one search setup.
// Aligned with destinations by index; null where unreachable. This is
// the one to use for ranking — N separate time() calls redo the setup.
times(
area: string,
origin: PointLike,
destinations: PointLike[],
options?: RouteOptions,
): (number | null)[];
// full trip: time, distance, snapped ends, and geometry on request.
route(area: string, from: PointLike, to: PointLike, options?: RouteOptions): Leg | null;
// resolve a coordinate onto the graph; null if nothing routable is near.
snap(area: string, at: PointLike, options?: RouteOptions): Snapped | null;
// every junction reachable ON FOOT within maxWalkMeters, measured along
// real paths. null only when the origin does not snap onto the network.
//
// Walking only, and profile defaults to "foot" here for that reason:
// the car graph is contracted, where a one-sided search returns the
// search space rather than the reachable set, so asking for "car"
// stops the script instead of answering wrongly.
//
// Capped at 5000 m — the neighbourhood is held in memory and grows with
// the square of the radius. Over that throws rather than clamping.
//
// This is the raw primitive: thousands of junctions, for bucketing and
// filtering in a script. For "what places can I walk to" prefer the
// walk_reach tool, which annotates POIs instead.
reach(area: string, origin: PointLike, options?: RouteOptions): Reached[] | null;
}
PointLike
// Routing coordinates share the geometry layer's coercion, so anything
// location-shaped works: a Result straight from query.execute, a Point, a
// Bound or Area (centre used), or a plain {lat, lon} object.
type PointLike = Locatable;
RouteOptions
interface RouteOptions {
profile?: "car" | "foot"; // default "car"
maxSnapMeters?: number; // reject an endpoint further than this from a road
maxWalkMeters?: number; // foot only; default 10000, max 25000 (reach(): default and max 5000)
geometry?: boolean; // route() only — off by default, a long route is 1000s of points
steps?: boolean; // route() only — the road log; costs about half a route again
}
Snapped
interface Snapped {
point: Point;
offsetMeters: number; // how far the coordinate moved to reach the graph
// The road this landed on. The router resolves this edge to choose the
// junction, so it costs nothing extra — and it is the answer to "which
// street is this", measured against the road's real shape rather than
// its bounding-box centre. null only on an artifact built before the
// edge-to-way mapping existed.
road: Road | null;
}
Road
interface Road {
osmId: number;
name: string; // "Dwight D. Eisenhower Highway"; "" on the 73% of ways with none
ref: string; // route number: "I 70", "US 40"; "" on an unnumbered street
highway: string; // OSM class: motorway, trunk, primary, residential, service
}
Step
// One run of a route along a single road. NOT a turn instruction: there
// are no turn angles, no lane tags and no "turn left". Consecutive
// stretches of the same road are one step, and slip roads are folded
// into the road they lead onto.
interface Step {
road: Road;
atMeters: number; // how far into the trip this road begins; steps tile
atMiles: number;
meters: number;
miles: number;
via: string; // the motorway junction reached through, e.g. "exit 232"; "" if none
}
Reached
interface Reached {
point: Point;
seconds: number;
minutes: number;
// Network distance derived from cost at walking pace, not measured from
// geometry — unpacking polylines for thousands of nodes would cost more
// than the search. Exact enough to pick candidates; use route() when a
// distance is going to be reported to someone.
meters: number;
miles: number;
}
Leg
interface Leg {
seconds: number;
minutes: number;
meters: number; // along the road, not straight-line
miles: number;
from: Snapped;
to: Snapped;
geometry: Point[]; // empty unless options.geometry was set
steps: Step[]; // empty unless options.steps was set
}
Budget
interface Budget {
remainingMs(): number; // ms until the VM is interrupted at the deadline (0 if past)
elapsedMs(): number; // ms this call has been running
}
URL
// The only web globals in the VM: WHATWG URL parsing (from goja_nodejs).
// There is no fetch — the runtime cannot reach the network.
declare class URL {
constructor(url: string, base?: string);
href: string;
protocol: string;
host: string;
hostname: string;
port: string;
pathname: string;
search: string;
hash: string;
searchParams: URLSearchParams;
toString(): string;
}
URLSearchParams
declare class URLSearchParams {
constructor(init?: string);
get(name: string): string | null;
getAll(name: string): string[];
has(name: string): boolean;
set(name: string, value: string): void;
append(name: string, value: string): void;
delete(name: string): void;
toString(): string;
}
Address
interface Address {
// Returns [parsedParts, found]. found is false (and parsedParts empty)
// when the address could not be parsed.
parse(fullAddress: string): [{ [key: string]: string }, boolean];
}
input
// ---------------------------------------------------------------------------
// Checks
//
// These apply only to a script run as a CHECK (server/checks) — a question
// asked once and then run against many addresses. A plain `runtime` tool call
// has no `input` and may export any JSON it likes.
// ---------------------------------------------------------------------------
// The house being checked. Go resolves the address and binds this before the
// VM runs, because the runtime has no geocoder and a script that guessed its
// own anchor would measure every number from the wrong place.
declare const input: CheckInput;
CheckInput
interface CheckInput {
address: string; // exactly what was typed
label: string; // what the geocoder resolved it to
lat: number; // the anchor. Measure from here.
lon: number;
area: string; // snake_case; every query and every route needs it
kind: "address" | "poi" | "street";
// False when this area's build carries no foot graph. A walking criterion
// must then report "unknown" rather than "fail": the house is not wrong,
// we simply cannot measure it.
walkGraph: boolean;
// Present only when the target was a listing URL.
listing?: CheckListing;
}
CheckListing
interface CheckListing {
source_url: string;
price?: number;
beds?: number;
baths?: number;
sqft?: number;
lot_size?: number;
lot_units?: string;
year_built?: number;
water_source?: string;
sewer?: string;
hoa_fee?: number;
hoa_frequency?: string;
taxes_annual?: number;
days_on_market?: number;
}
Criterion
// One named finding.
//
// `status` is OPTIONAL, and that is the point: a criterion carrying one is a
// test, a criterion carrying only a value is a measurement. Omit it whenever
// the question has no threshold ("how many", "how far") rather than inventing
// one — measurements are what make a column sortable across addresses.
interface Criterion {
name: string;
status?: "pass" | "fail" | "unknown";
// Omit entirely when there is nothing to measure. Never write 0 as a
// placeholder: "0 min walk" renders as the BEST possible answer.
value?: number | null;
unit?: string;
// Built from the numbers just computed, so it cannot contradict the value
// printed beside it.
detail?: string;
// Evidence: where the thing that matched actually is. Dropped by the server
// if it is further than 200km from the anchor, because at that distance it
// names something else.
at?: { lat: number; lon: number };
items?: CriterionItem[]; // capped at 25
}
CriterionItem
interface CriterionItem {
label: string;
value?: number | null;
unit?: string;
at?: { lat: number; lon: number };
}
CheckPayload
// What a check exports. Note there is no overall pass/fail: the server
// derives it (any fail -> fail, else any unknown -> unknown, else pass), so a
// summary boolean can never disagree with the criteria it summarises.
interface CheckPayload {
criteria: Criterion[]; // at least one, at most 12
// Anything else, stored and returned untouched. The escape hatch for what
// the criteria shape cannot express.
data?: unknown;
}