rhombus-node-mcp 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/constants.js +2 -0
- package/dist/index.js +23 -534
- package/dist/logger.js +27 -0
- package/dist/network.js +81 -0
- package/dist/resources/getResources.js +19 -0
- package/dist/resources/llms.pdf.js +19 -0
- package/dist/resources/routes.json.js +31 -0
- package/dist/tools/clips-tool.js +62 -0
- package/dist/tools/create-tool.js +80 -0
- package/dist/tools/devices/camera-tool/camera-tool.js +218 -0
- package/dist/tools/devices/camera-tool/types.js +172 -0
- package/dist/tools/devices/get-entity-tool.js +117 -0
- package/dist/tools/events-tool.js +101 -0
- package/dist/tools/faces-tool.js +140 -0
- package/dist/tools/get-org-information.js +18 -0
- package/dist/tools/getTools.js +22 -0
- package/dist/tools/location-tool.js +34 -0
- package/dist/tools/policy-alerts-tool.js +57 -0
- package/dist/tools/reboot-cameras.js +63 -0
- package/dist/tools/time-tool.js +57 -0
- package/dist/types/deviceType.js +14 -0
- package/dist/types.js +16 -13
- package/dist/util.js +111 -0
- package/dist/utils/confirmation.js +45 -0
- package/package.json +3 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const VideoFacetSettings = z
|
|
3
|
+
.object({
|
|
4
|
+
// blocked_debounce_time_ms: z.number().int().nullable(),
|
|
5
|
+
// blocked_threshold: z.number().int().nullable(),
|
|
6
|
+
// dewarpMode: z.enum([
|
|
7
|
+
// 'NO_TRANSFORM',
|
|
8
|
+
// 'NORMAL',
|
|
9
|
+
// 'PANORAMA',
|
|
10
|
+
// 'SUB_REGION',
|
|
11
|
+
// 'VERTICAL_PANORAMA',
|
|
12
|
+
// 'TRANSVERSE_MERCATOR',
|
|
13
|
+
// 'MERCATOR',
|
|
14
|
+
// 'EQUIRECTANGULAR',
|
|
15
|
+
// ]).nullable(),
|
|
16
|
+
// disabled_schedule: z.array(
|
|
17
|
+
// z.object({
|
|
18
|
+
// minuteOfWeekStart: z.number().int().nullable(),
|
|
19
|
+
// minuteOfWeekStop: z.number().int().nullable(),
|
|
20
|
+
// }).nullable()
|
|
21
|
+
// ).nullable(),
|
|
22
|
+
// disabled_schedule_inverted: z.boolean().nullable(),
|
|
23
|
+
// disabled_schedule_uuid: z.string().nullable(),
|
|
24
|
+
// exposure_level: z.number().nullable(),
|
|
25
|
+
// fisheye_display_mode: z.enum(['RAW', 'IMMERSIVE', 'TILES', 'RAW_PANO']).nullable(),
|
|
26
|
+
// floorplan_homography: z.array(z.array(z.number().nullable()).nullable()).nullable(),
|
|
27
|
+
hdr_enabled: z.boolean().nullable(),
|
|
28
|
+
img_brightness: z
|
|
29
|
+
.number()
|
|
30
|
+
.nullable()
|
|
31
|
+
.describe("Ranges from [-255, 255]. The image is described as a range from [0, 255], and the brightness slider will ADD this value to whatever the original value is. Use this accordingly."),
|
|
32
|
+
img_contrast: z
|
|
33
|
+
.number()
|
|
34
|
+
.nullable()
|
|
35
|
+
.describe("Ranges from [0, 128]. In general, 64 works for most situations, but adjust accordingly."),
|
|
36
|
+
img_saturation: z
|
|
37
|
+
.number()
|
|
38
|
+
.nullable()
|
|
39
|
+
.describe("Ranges from [0, 255]. In general, 64 works for most situations, but adjust accordingly."),
|
|
40
|
+
img_sharpness: z
|
|
41
|
+
.number()
|
|
42
|
+
.nullable()
|
|
43
|
+
.describe("Ranges from [0, 11]. In general, 6 works for most situations, but adjust accordingly."),
|
|
44
|
+
// metering_config: z.object({
|
|
45
|
+
// rotation: z.number().nullable(),
|
|
46
|
+
// table: z.string().nullable(),
|
|
47
|
+
// }).nullable(),
|
|
48
|
+
// motor_config: z.object({
|
|
49
|
+
// af_enabled: z.boolean().nullable(),
|
|
50
|
+
// af_region: z.object({
|
|
51
|
+
// x: z.number().int().min(0).max(10000).nullable(),
|
|
52
|
+
// y: z.number().int().min(0).max(10000).nullable(),
|
|
53
|
+
// width: z.number().int().min(0).max(10000).nullable(),
|
|
54
|
+
// height: z.number().int().min(0).max(10000).nullable(),
|
|
55
|
+
// }).nullable(),
|
|
56
|
+
// focus: z.number().nullable(),
|
|
57
|
+
// piris: z.number().nullable(),
|
|
58
|
+
// zoom: z.number().nullable(),
|
|
59
|
+
// }).nullable(),
|
|
60
|
+
// mounting_direction: z.enum(['DOWN', 'UP', 'SIDEWAYS', 'UNKNOWN']).nullable(),
|
|
61
|
+
night_exposure_level: z.number().nullable(),
|
|
62
|
+
night_img_brightness: z.number().nullable(),
|
|
63
|
+
night_img_contrast: z.number().nullable(),
|
|
64
|
+
night_img_saturation: z.number().nullable(),
|
|
65
|
+
night_img_sharpness: z.number().nullable(),
|
|
66
|
+
// night_metering_config: CameraMeteringConfigTypeSchema.nullable(),
|
|
67
|
+
night_sensor_gain_max: z.number().nullable(),
|
|
68
|
+
night_shutter_time_max: z.number().nullable(),
|
|
69
|
+
night_shutter_time_min: z.number().nullable(),
|
|
70
|
+
// object_search: z.boolean().nullable(),
|
|
71
|
+
// privacy_windows: z.array(
|
|
72
|
+
// z.object({
|
|
73
|
+
// x: z.number().int().min(0).max(10000).nullable(),
|
|
74
|
+
// y: z.number().int().min(0).max(10000).nullable(),
|
|
75
|
+
// width: z.number().int().min(0).max(10000).nullable(),
|
|
76
|
+
// height: z.number().int().min(0).max(10000).nullable(),
|
|
77
|
+
// }).nullable()
|
|
78
|
+
// ).nullable(),
|
|
79
|
+
// ptz_config: z.object({
|
|
80
|
+
// offset_x_percent: z.number().nullable(),
|
|
81
|
+
// offset_y_percent: z.number().nullable(),
|
|
82
|
+
// rotation: z.number().nullable(),
|
|
83
|
+
// size_percent: z.number().nullable(),
|
|
84
|
+
// }).nullable(),
|
|
85
|
+
// region_for_occupancy: z.object({
|
|
86
|
+
// inverted: z.boolean().nullable(),
|
|
87
|
+
// polygons: z.array(
|
|
88
|
+
// z.object({
|
|
89
|
+
// coordinates: z.array(
|
|
90
|
+
// z.object({
|
|
91
|
+
// x: z.number().nullable(),
|
|
92
|
+
// y: z.number().nullable(),
|
|
93
|
+
// }).nullable()
|
|
94
|
+
// ).nullable(),
|
|
95
|
+
// }).nullable()
|
|
96
|
+
// ).nullable(),
|
|
97
|
+
// }).nullable(),
|
|
98
|
+
// region_of_interest: z.object({
|
|
99
|
+
// inverted: z.boolean().nullable(),
|
|
100
|
+
// polygons: z.array(
|
|
101
|
+
// z.object({
|
|
102
|
+
// coordinates: z.array(
|
|
103
|
+
// z.object({
|
|
104
|
+
// x: z.number().nullable(),
|
|
105
|
+
// y: z.number().nullable(),
|
|
106
|
+
// }).nullable()
|
|
107
|
+
// ).nullable(),
|
|
108
|
+
// }).nullable()
|
|
109
|
+
// ).nullable(),
|
|
110
|
+
// }).nullable(),
|
|
111
|
+
// region_of_interest_groups: z.array(
|
|
112
|
+
// z.object({
|
|
113
|
+
// inclusive: z.boolean().nullable(),
|
|
114
|
+
// regionsOfInterest: z.array(
|
|
115
|
+
// z.object({
|
|
116
|
+
// activities: z.array(z.string()).nullable(), // Assuming ActivityEnumSchema is just string
|
|
117
|
+
// name: z.string().nullable(),
|
|
118
|
+
// polygon: z.object({
|
|
119
|
+
// coordinates: z.array(
|
|
120
|
+
// z.object({
|
|
121
|
+
// x: z.number().nullable(),
|
|
122
|
+
// y: z.number().nullable(),
|
|
123
|
+
// }).nullable()
|
|
124
|
+
// ).nullable(),
|
|
125
|
+
// }).nullable(),
|
|
126
|
+
// uuid: z.string().nullable(),
|
|
127
|
+
// }).nullable()
|
|
128
|
+
// ).nullable(),
|
|
129
|
+
// type: z.enum(['ACTIVITY', 'REPORTING', 'CROSSING']).nullable(),
|
|
130
|
+
// }).nullable()
|
|
131
|
+
// ).nullable(),
|
|
132
|
+
resolution: z
|
|
133
|
+
.object({
|
|
134
|
+
width: z.number().int().positive().nullable(),
|
|
135
|
+
height: z.number().int().positive().nullable(),
|
|
136
|
+
})
|
|
137
|
+
.nullable(),
|
|
138
|
+
rotation: z.number().nullable(),
|
|
139
|
+
segment_max_bytes: z.number().nullable(),
|
|
140
|
+
sensor_gain_max: z.number().nullable(),
|
|
141
|
+
shutter_time_max: z.number().nullable(),
|
|
142
|
+
shutter_time_min: z.number().nullable(),
|
|
143
|
+
snapshot_height: z.number().nullable(),
|
|
144
|
+
snapshot_interval_secs: z.number().nullable(),
|
|
145
|
+
// tile_views: z.array(
|
|
146
|
+
// z.object({
|
|
147
|
+
// aspectRatio: z.object({
|
|
148
|
+
// width: z.number().nullable(),
|
|
149
|
+
// height: z.number().nullable(),
|
|
150
|
+
// }).nullable(),
|
|
151
|
+
// pitchDegrees: z.number().nullable(),
|
|
152
|
+
// rollDegrees: z.number().nullable(),
|
|
153
|
+
// verticalFieldOfViewDegrees: z.number().nullable(),
|
|
154
|
+
// yawDegrees: z.number().nullable(),
|
|
155
|
+
// }).nullable()
|
|
156
|
+
// ).nullable(),
|
|
157
|
+
// updatedSetMethodMap: z.record(z.boolean().nullable()).nullable(),
|
|
158
|
+
video_persist_disabled: z.boolean().nullable(),
|
|
159
|
+
// wdr_enabled: z.boolean().nullable(), TODO: find a way to tell the LLM which devices support this
|
|
160
|
+
wdr_strength: z.number().nullable().describe("Ranges from [0, 128]."),
|
|
161
|
+
zero_motion_video_bitrate_percent: z.number().nullable(),
|
|
162
|
+
})
|
|
163
|
+
.nullable()
|
|
164
|
+
.describe("This describes the kind of settings that you can manipulate for a camera. If the user asks to change settings, only change what is necessary and don't pass in any fields that you do not need.");
|
|
165
|
+
export const ExternalUpdateableFacetedUserConfigSchema = z.object({
|
|
166
|
+
/** Update Video Settings */
|
|
167
|
+
videoFacetSettings: z
|
|
168
|
+
.object({
|
|
169
|
+
v0: VideoFacetSettings,
|
|
170
|
+
})
|
|
171
|
+
.describe("For each of the object keys: v0, v1, v2, v3, they correspond to a specific facet of the camera, since a camera may have multiple facets, up to 4. Hence, why they are zero-indexed."),
|
|
172
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { postApi } from "../../network.js";
|
|
3
|
+
import { createToolArgs, createToolTextContent } from "../../util.js";
|
|
4
|
+
import DeviceType from "../../types/deviceType.js";
|
|
5
|
+
async function getCameraList(requestModifiers) {
|
|
6
|
+
return {
|
|
7
|
+
cameras: (await postApi("/camera/getMinimalCameraStateList", "{}", requestModifiers)).cameraStates.filter((camera) => !!camera.locationUuid),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
async function getDoorbellCameras(requestModifiers) {
|
|
11
|
+
return {
|
|
12
|
+
doorbellCameras: (await postApi("/doorbellcamera/getMinimalStateList", "{}", requestModifiers)).minimalStates.filter((doorbellCamera) => !!doorbellCamera.locationUuid),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async function getBadgeReaders(requestModifiers) {
|
|
16
|
+
return await postApi("/badgereader/getMinimalStateList", "{}", requestModifiers).then(response => {
|
|
17
|
+
return {
|
|
18
|
+
badgeReaders: response.minimalStates.filter((badgeReader) => !!badgeReader.locationUuid),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function getAccessControlledDoors(requestModifiers) {
|
|
23
|
+
return await postApi("/component/findAccessControlledDoors", "{}", requestModifiers);
|
|
24
|
+
}
|
|
25
|
+
async function getAudioGateways(requestModifiers) {
|
|
26
|
+
return await postApi("/audiogateway/getMinimalAudioGatewayStateList", "{}", requestModifiers).then(response => {
|
|
27
|
+
return {
|
|
28
|
+
audioGateways: response.audioGatewayStates.filter((camera) => !!camera.locationUuid),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function getDoorSensors(requestModifiers) {
|
|
33
|
+
return await postApi("/door/getMinimalDoorStateList", "{}", requestModifiers).then(response => {
|
|
34
|
+
return {
|
|
35
|
+
doorStates: response.doorStates.filter((door) => !!door.locationUuid),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function getEnvironmentalSensors(requestModifiers) {
|
|
40
|
+
return await postApi("/climate/getMinimalClimateStateList", "{}", requestModifiers).then(response => {
|
|
41
|
+
return {
|
|
42
|
+
climateStates: response.climateStates.filter((sensor) => !!sensor.locationUuid),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function getMotionSensors(requestModifiers) {
|
|
47
|
+
return await postApi("/occupancy/getMinimalOccupancySensorStateList", "{}", requestModifiers).then(response => {
|
|
48
|
+
return {
|
|
49
|
+
occupancySensorStates: response.occupancySensorStates.filter((occupancySensor) => !!occupancySensor.locationUuid),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function getButtons(requestModifiers) {
|
|
54
|
+
return await postApi("/button/getMinimalButtonStateList", "{}", requestModifiers).then(response => {
|
|
55
|
+
return {
|
|
56
|
+
buttonStates: response.states.filter((button) => !!button.locationUuid),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function getKeypads(requestModifiers) {
|
|
61
|
+
return await postApi("/keypad/getKeypadsForOrg", "{}", requestModifiers).then(response => {
|
|
62
|
+
return {
|
|
63
|
+
keypadStates: response.keypads.filter((keypad) => !!keypad.locationUuid),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function createTool(server) {
|
|
68
|
+
server.tool("get-entity-tool", `
|
|
69
|
+
Retrieves entities (or devices) of certain types.
|
|
70
|
+
Can request multiple entity types at once.
|
|
71
|
+
The return structure is a JSON string that continues the states of the requested entities.
|
|
72
|
+
This data is exact. Whatever entities exist will be returned here.`, createToolArgs({
|
|
73
|
+
entityTypes: z
|
|
74
|
+
.array(z.nativeEnum(DeviceType).describe("The entity type to retreive"))
|
|
75
|
+
.describe("What type of entities to retrieve."),
|
|
76
|
+
}), async ({ entityTypes, requestModifiers }) => {
|
|
77
|
+
const promises = [];
|
|
78
|
+
if (entityTypes.includes(DeviceType.CAMERA)) {
|
|
79
|
+
promises.push(getCameraList(requestModifiers));
|
|
80
|
+
}
|
|
81
|
+
if (entityTypes.includes(DeviceType.DOORBELL_CAMERA)) {
|
|
82
|
+
promises.push(getDoorbellCameras(requestModifiers));
|
|
83
|
+
}
|
|
84
|
+
if (entityTypes.includes(DeviceType.BADGE_READER)) {
|
|
85
|
+
promises.push(getBadgeReaders(requestModifiers));
|
|
86
|
+
}
|
|
87
|
+
if (entityTypes.includes(DeviceType.ACCESS_CONTROL_DOOR)) {
|
|
88
|
+
promises.push(getAccessControlledDoors(requestModifiers));
|
|
89
|
+
}
|
|
90
|
+
if (entityTypes.includes(DeviceType.AUDIO_GATEWAY)) {
|
|
91
|
+
promises.push(getAudioGateways(requestModifiers));
|
|
92
|
+
}
|
|
93
|
+
if (entityTypes.includes(DeviceType.DOOR_SENSOR)) {
|
|
94
|
+
promises.push(getDoorSensors(requestModifiers));
|
|
95
|
+
}
|
|
96
|
+
if (entityTypes.includes(DeviceType.ENVIRONMENTAL_SENSOR)) {
|
|
97
|
+
promises.push(getEnvironmentalSensors(requestModifiers));
|
|
98
|
+
}
|
|
99
|
+
if (entityTypes.includes(DeviceType.MOTION_SENSOR)) {
|
|
100
|
+
promises.push(getMotionSensors(requestModifiers));
|
|
101
|
+
}
|
|
102
|
+
if (entityTypes.includes(DeviceType.BUTTON)) {
|
|
103
|
+
promises.push(getButtons(requestModifiers));
|
|
104
|
+
}
|
|
105
|
+
if (entityTypes.includes(DeviceType.KEYPAD)) {
|
|
106
|
+
promises.push(getKeypads(requestModifiers));
|
|
107
|
+
}
|
|
108
|
+
const responses = Promise.all(promises);
|
|
109
|
+
const ret = {
|
|
110
|
+
...(await responses).reduce((prev, curr) => ({
|
|
111
|
+
...prev,
|
|
112
|
+
...curr
|
|
113
|
+
}), {})
|
|
114
|
+
};
|
|
115
|
+
return createToolTextContent(JSON.stringify(ret));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createToolArgs } from "../util.js";
|
|
3
|
+
import { postApi } from "../network.js";
|
|
4
|
+
import { THREE_HOURS_MS, FIVE_SECONDS_MS } from "../constants.js";
|
|
5
|
+
async function getFaceEvents(_locationUuid, requestModifiers) {
|
|
6
|
+
const nowMs = Date.now();
|
|
7
|
+
const rangeStartMs = nowMs - THREE_HOURS_MS;
|
|
8
|
+
const rangeEndMs = nowMs - FIVE_SECONDS_MS;
|
|
9
|
+
const body = {
|
|
10
|
+
pageRequest: {
|
|
11
|
+
lastEvaluatedKey: undefined,
|
|
12
|
+
maxPageSize: 75,
|
|
13
|
+
},
|
|
14
|
+
searchFilter: {
|
|
15
|
+
deviceUuids: [],
|
|
16
|
+
faceNames: [],
|
|
17
|
+
labels: [],
|
|
18
|
+
locationUuids: [],
|
|
19
|
+
personUuids: [],
|
|
20
|
+
timestampFilter: {
|
|
21
|
+
rangeStart: rangeStartMs,
|
|
22
|
+
rangeEnd: rangeEndMs,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const response = await postApi("/faceRecognition/faceEvent/findFaceEventsByOrg", body, requestModifiers).then(response => {
|
|
27
|
+
return {
|
|
28
|
+
faceEvents: (response.faceEvents || []).map((event) => ({
|
|
29
|
+
...event,
|
|
30
|
+
eventTimestamp: new Date(event.eventTimestamp).toString(),
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
async function getAccessControlEvents(doorUuid, requestModifiers) {
|
|
37
|
+
const body = {
|
|
38
|
+
limit: 50,
|
|
39
|
+
accessControlledDoorUuid: doorUuid,
|
|
40
|
+
};
|
|
41
|
+
const response = await postApi("/component/findComponentEventsByAccessControlledDoor", body, requestModifiers).then(response => ({
|
|
42
|
+
componentEvents: (response.componentEvents || []).map((event) => ({
|
|
43
|
+
...event,
|
|
44
|
+
timestamp: new Date(event.timestampMs).toString(),
|
|
45
|
+
})),
|
|
46
|
+
}));
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
export function createTool(server) {
|
|
50
|
+
server.tool("events-tool", "event data for certain types of information like faces, license plates, and access-control events", createToolArgs({
|
|
51
|
+
eventType: z.enum(["faces", "people", "access-control"]),
|
|
52
|
+
locationUuid: z.optional(z.string()),
|
|
53
|
+
accessControlledDoorUuid: z.optional(z.string()),
|
|
54
|
+
}), async ({ eventType, locationUuid, accessControlledDoorUuid, requestModifiers }) => {
|
|
55
|
+
if (eventType === "faces" || eventType === "people") {
|
|
56
|
+
const response = await getFaceEvents(locationUuid, requestModifiers);
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: JSON.stringify(response),
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (eventType === "access-control") {
|
|
67
|
+
if (!accessControlledDoorUuid) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: JSON.stringify({
|
|
73
|
+
needUserInput: true,
|
|
74
|
+
commandForUser: "Which door are you asking about?",
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const events = await getAccessControlEvents(accessControlledDoorUuid, requestModifiers);
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify(events),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: JSON.stringify({}),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { postApi } from "../network.js";
|
|
3
|
+
import { createToolArgs } from "../util.js";
|
|
4
|
+
var RequestType;
|
|
5
|
+
(function (RequestType) {
|
|
6
|
+
RequestType["GET_FACE_EVENTS"] = "get-face-events";
|
|
7
|
+
RequestType["GET_REGISTERED_FACES"] = "get-registered-faces";
|
|
8
|
+
})(RequestType || (RequestType = {}));
|
|
9
|
+
// Zod Schema for 'get-registered-faces' arguments (no specific args)
|
|
10
|
+
const GetRegisteredFacesArgsSchema = z.object({
|
|
11
|
+
requestType: z.literal(RequestType.GET_REGISTERED_FACES),
|
|
12
|
+
});
|
|
13
|
+
// --- GetFaceEventsArgs Schema ---
|
|
14
|
+
// All 'get-face-events' specific arguments are directly in this object,
|
|
15
|
+
// marked as nullable since they are conditional based on requestType.
|
|
16
|
+
const GetFaceEventsArgs = z.object({
|
|
17
|
+
// Arguments for 'get-face-events' (flattened from original searchFilter)
|
|
18
|
+
deviceUuids: z
|
|
19
|
+
.array(z.string())
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional filter by a set of device UUIDs. Only face events from these devices will be returned. Applicable when requestType is 'get-face-events'."),
|
|
22
|
+
faceNameContains: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Optional filter for face events where the detected face's name contains this substring. The search is performed only if the value is at least 3 characters long after trimming spaces. This takes precedence over 'faceNames' if both are specified. Applicable when requestType is 'get-face-events'."),
|
|
26
|
+
faceNames: z
|
|
27
|
+
.array(z.string())
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Optional filter by a set of specific person names. Only face events associated with these names will be returned. Applicable when requestType is 'get-face-events'."),
|
|
30
|
+
hasEmbedding: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Optional filter by the presence (true) or absence (false) of a face embedding associated with the event. Applicable when requestType is 'get-face-events'."),
|
|
34
|
+
hasName: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Optional filter by the presence (true) or absence (false) of a person name associated with the face event. Applicable when requestType is 'get-face-events'."),
|
|
38
|
+
labels: z
|
|
39
|
+
.array(z.string())
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Optional filter by a set of labels associated with the face event. Applicable when requestType is 'get-face-events'."),
|
|
42
|
+
locationUuids: z
|
|
43
|
+
.array(z.string())
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Optional filter by a set of location UUIDs. Only face events from these locations will be returned. Applicable when requestType is 'get-face-events'."),
|
|
46
|
+
personUuids: z
|
|
47
|
+
.array(z.string())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Optional filter by a set of person UUIDs. Only face events associated with these specific people will be returned. Applicable when requestType is 'get-face-events'."),
|
|
50
|
+
// Arguments for 'get-face-events' (flattened from original timestampFilter)
|
|
51
|
+
rangeEnd: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("The end of the time range (inclusive) for filtering face events. Expected format is a string containing a timestamp in milliseconds since epoch. Applicable when requestType is 'get-face-events'. If not specified, the filter defaults to the last 7 days."),
|
|
55
|
+
rangeStart: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("The start of the time range (inclusive) for filtering face events. Expected format is a string containing a timestamp in milliseconds since epoch. Applicable when requestType is 'get-face-events'. If not specified, the filter defaults to the last 7 days."),
|
|
59
|
+
// No specific arguments for 'get-registered-faces' beyond requestType itself.
|
|
60
|
+
});
|
|
61
|
+
// --- Extracted PostAPI Calls into Functions ---
|
|
62
|
+
async function getFaceEvents(args, requestModifiers) {
|
|
63
|
+
// Directly pass the args object as requested by the user
|
|
64
|
+
return await postApi("/faceRecognition/faceEvent/findFaceEventsByOrg", args, requestModifiers);
|
|
65
|
+
}
|
|
66
|
+
async function getRegisteredFaces(args, requestModifiers) {
|
|
67
|
+
// No specific arguments for this API call, so send an empty object.
|
|
68
|
+
return await postApi("/faceRecognition/person/findPeopleByOrg", {}, requestModifiers);
|
|
69
|
+
}
|
|
70
|
+
// --- Main Tool Definition ---
|
|
71
|
+
export function createTool(server) {
|
|
72
|
+
server.tool("faces-tool", `
|
|
73
|
+
This tool interacts with the Rhombus face recognition system to retrieve information about face events and registered faces. It has two primary modes of operation, determined by the "requestType" parameter:
|
|
74
|
+
|
|
75
|
+
If the requestType is "get-face-events":
|
|
76
|
+
|
|
77
|
+
This tool retrieves detailed face events recorded by your Rhombus cameras, such as detections and recognitions. It provides comprehensive information about each event, including details about the detected face, the person matched (if any), and the event context.
|
|
78
|
+
|
|
79
|
+
You can filter face events using parameters like 'deviceUuids', 'faceNameContains', 'faceNames', 'hasEmbedding', 'hasName', 'labels', 'locationUuids', 'personUuids', and a time range using 'rangeStart' and 'rangeEnd' (timestamps in milliseconds).
|
|
80
|
+
|
|
81
|
+
The tool returns a JSON object with the following structure and important fields:
|
|
82
|
+
* **lastEvaluatedKey (string | null):** A key for pagination; if not null, it can be used to retrieve the next page of results.
|
|
83
|
+
* **faceEvents (array of objects | null):** An array where each object represents a single face event. Each face event object contains the following important fields:
|
|
84
|
+
* **uuid (string):** The unique identifier for this specific face event.
|
|
85
|
+
* **eventTimestamp (int64):** The timestamp (in milliseconds since epoch) when the face event occurred.
|
|
86
|
+
* **faceName (string | null):** The name of the person that matched the face image, if a match was found.
|
|
87
|
+
* **personUuid (string | null):** The UUID of the person that matched the face image, if a match was found.
|
|
88
|
+
* **deviceUuid (string):** The UUID of the device (e.g., camera) where the face event was detected.
|
|
89
|
+
* **locationUuid (string | null):** The UUID of the location where the face event occurred.
|
|
90
|
+
* **orgUuid (string | null):** The UUID of the organization associated with the event.
|
|
91
|
+
* **detectionConfidence (float | null):** A confidence value (between 0.0 and 1.0) indicating the likelihood that the detected image is a face.
|
|
92
|
+
* **thumbnailS3Key (string | null):** The S3 key for accessing the thumbnail image of the detected face.
|
|
93
|
+
* **selectedPersonMatch (object | null):** An object containing details about the top person match found for the face image, if any. Important fields within this object include:
|
|
94
|
+
* **confidence (float | null):** The match confidence level, expressed as a value in the range [0,1].
|
|
95
|
+
* **faceId (string | null):** The face ID of the matched person.
|
|
96
|
+
* **name (string | null):** The name of the matched person.
|
|
97
|
+
* **uuid (string | null):** The UUID of the matched person.
|
|
98
|
+
|
|
99
|
+
If the requestType is "get-registered-faces":
|
|
100
|
+
|
|
101
|
+
This tool retrieves a list of all people (registered faces) currently known to the Rhombus system for your organization. This list includes information about each registered person.
|
|
102
|
+
|
|
103
|
+
This tool takes no arguments.
|
|
104
|
+
|
|
105
|
+
The tool returns a JSON object with the following structure and important fields:
|
|
106
|
+
* **people (array of objects | null):** An array where each object represents a registered person. Each person object contains the following important fields:
|
|
107
|
+
* **uuid (string):** The unique identifier for the registered person.
|
|
108
|
+
* **name (string | null):** The name associated with the registered person.
|
|
109
|
+
* **createdOn (date-time | null):** The date and time when the person was registered.
|
|
110
|
+
* **updatedOn (date-time | null):** The date and time when the person's information was last updated.
|
|
111
|
+
* **orgUuid (string | null):** The UUID of the organization to which the person belongs.
|
|
112
|
+
|
|
113
|
+
`,
|
|
114
|
+
// FacesToolArgs directly passed here
|
|
115
|
+
createToolArgs({
|
|
116
|
+
requestType: z.nativeEnum(RequestType),
|
|
117
|
+
args: z.union([
|
|
118
|
+
z.object({
|
|
119
|
+
args: GetFaceEventsArgs,
|
|
120
|
+
}),
|
|
121
|
+
z.object({
|
|
122
|
+
requestType: z.literal(RequestType.GET_REGISTERED_FACES),
|
|
123
|
+
args: GetRegisteredFacesArgsSchema,
|
|
124
|
+
}),
|
|
125
|
+
]),
|
|
126
|
+
}), async ({ requestModifiers, requestType, args }) => {
|
|
127
|
+
let ret;
|
|
128
|
+
if (requestType === "get-face-events") {
|
|
129
|
+
// Pass the args directly, as requested
|
|
130
|
+
ret = await getFaceEvents(args, requestModifiers);
|
|
131
|
+
}
|
|
132
|
+
else if (requestType === "get-registered-faces") {
|
|
133
|
+
// Pass the args (will effectively be an empty object for this call)
|
|
134
|
+
ret = await getRegisteredFaces(args, requestModifiers);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: JSON.stringify(ret) }],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createToolArgs } from "../util.js";
|
|
2
|
+
import { postApi } from "../network.js";
|
|
3
|
+
async function getOrg(requestModifiers) {
|
|
4
|
+
return await postApi("/org/getOrgV2", {}, requestModifiers);
|
|
5
|
+
}
|
|
6
|
+
export function createTool(server) {
|
|
7
|
+
server.tool("get-org-information", "Get general information about the organization including org name, camera configuration defaults, contact information, and org settings.", createToolArgs({}), async ({ requestModifiers }) => {
|
|
8
|
+
const org = await getOrg(requestModifiers);
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: "text",
|
|
13
|
+
text: JSON.stringify(org),
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getFilePathsInDirectory } from "../util.js";
|
|
4
|
+
async function getTools() {
|
|
5
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const filePaths = getFilePathsInDirectory(currentDir);
|
|
7
|
+
const tools = [];
|
|
8
|
+
for (const filePath of filePaths) {
|
|
9
|
+
// ensure it's a js file
|
|
10
|
+
if (!filePath.endsWith(".js"))
|
|
11
|
+
continue;
|
|
12
|
+
const imported = (await import(filePath));
|
|
13
|
+
if (imported.createTool !== undefined) {
|
|
14
|
+
tools.push({
|
|
15
|
+
name: filePath,
|
|
16
|
+
create: imported.createTool,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return tools;
|
|
21
|
+
}
|
|
22
|
+
export default getTools;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createToolArgs } from "../util.js";
|
|
3
|
+
import { postApi } from "../network.js";
|
|
4
|
+
async function getLocations(requestModifiers) {
|
|
5
|
+
return await postApi("/location/getLocationsV2", {}, requestModifiers);
|
|
6
|
+
}
|
|
7
|
+
export function createTool(server) {
|
|
8
|
+
server.tool("location-tool", `This tool performs operations on locations.
|
|
9
|
+
- 'get': Retrieves all locations.`, createToolArgs({
|
|
10
|
+
action: z.enum(["get", "update"]),
|
|
11
|
+
locationUpdate: z
|
|
12
|
+
.object({
|
|
13
|
+
uuid: z.string(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
})
|
|
16
|
+
.optional(),
|
|
17
|
+
}), async ({ action, requestModifiers }) => {
|
|
18
|
+
let ret;
|
|
19
|
+
switch (action) {
|
|
20
|
+
case "get":
|
|
21
|
+
ret = await getLocations(requestModifiers);
|
|
22
|
+
break;
|
|
23
|
+
case "update":
|
|
24
|
+
ret = { error: true, status: "not implemented" };
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
ret = { error: true, status: `unsupported location tool call: ${action}` };
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: JSON.stringify(ret) }],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { postApi } from "../network.js";
|
|
3
|
+
import { createToolArgs } from "../util.js";
|
|
4
|
+
const PolicyAlertsArgs = z.object({
|
|
5
|
+
afterTimestampMs: z
|
|
6
|
+
.number()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("The start of the time range (in milliseconds since epoch) for which to retrieve alerts. Only alerts that occurred AFTER this timestamp will be returned."),
|
|
9
|
+
beforeTimestampMs: z
|
|
10
|
+
.number()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("The end of the time range (in milliseconds since epoch) for which to retrieve alerts. Only alerts that occurred BEFORE this timestamp will be returned."),
|
|
13
|
+
deviceFilter: z
|
|
14
|
+
.array(z.string())
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("A list of UUIDs representing the specific devices to filter alerts by. Only alerts emitted by these devices will be returned."),
|
|
17
|
+
locationFilter: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("A list of UUIDs representing the specific locations to filter alerts by. Only alerts associated with these locations will be returned."),
|
|
21
|
+
maxResults: z
|
|
22
|
+
.number()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("The maximum number of policy alerts to return. The system may default to a reasonable number (e.g., 20) if not specified, but there is a hard cap (e.g., 100) on the maximum results the API will return."),
|
|
25
|
+
// these may be pagination args, we'll leave them out for now
|
|
26
|
+
// lastTimestampMs: z.number().optional(),
|
|
27
|
+
// lastUuid: z.number().optional(),
|
|
28
|
+
});
|
|
29
|
+
async function getPolicyAlerts(args, requestModifiers) {
|
|
30
|
+
return await postApi("/event/getPolicyAlertsV2", args, requestModifiers);
|
|
31
|
+
}
|
|
32
|
+
export function createTool(server) {
|
|
33
|
+
server.tool("policy-alerts-tool", `
|
|
34
|
+
Retrieves Rhombus policy alerts. Policy alerts in the Rhombus system are generated based on user-defined alert
|
|
35
|
+
policies configured in the Rhombus Console. These policies trigger alerts when specific events occur, such as:
|
|
36
|
+
* AI & Computer Vision Events: Based on intelligent video analytics for motion, people, vehicles, facial recognition,
|
|
37
|
+
license plate recognition, or unusual behavior.
|
|
38
|
+
* Device Status Changes: Like camera disconnections or sensor low battery.
|
|
39
|
+
* Physical or Visual Tamper: Detection of physical movement of a device or obstruction of a camera's field of view.
|
|
40
|
+
* Access Control Events: Such as unauthorized access attempts in restricted areas.
|
|
41
|
+
|
|
42
|
+
Please note, this is not an exhaustive list, and there may be other types of triggers or events that generate
|
|
43
|
+
policy alerts within the Rhombus system.
|
|
44
|
+
|
|
45
|
+
This tool allows you to filter existing alerts by a specific time range (before or after a timestamp in milliseconds),
|
|
46
|
+
by a list of device UUIDs, or by a list of location UUIDs.
|
|
47
|
+
You can also specify the maximum number of results to return.
|
|
48
|
+
The output is provided in JSON format.`, createToolArgs({
|
|
49
|
+
...PolicyAlertsArgs.shape,
|
|
50
|
+
}), async ({ requestModifiers, ...args }) => {
|
|
51
|
+
let ret;
|
|
52
|
+
ret = await getPolicyAlerts(args, requestModifiers);
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(ret) }],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|