rhombus-node-mcp 0.1.22 → 0.1.24
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 +8 -0
- package/dist/api/access-control-tool-api.js +256 -0
- package/dist/api/alarm-monitoring-tool-api.js +65 -0
- package/dist/api/camera-tool-api.js +35 -15
- package/dist/api/camera-uptime-tool-api.js +112 -0
- package/dist/api/clips-tool-api.js +110 -21
- package/dist/api/create-tool-api.js +143 -16
- package/dist/api/door-tool-api.js +66 -0
- package/dist/api/events-tool-api.js +151 -28
- package/dist/api/faces-tool-api.js +118 -90
- package/dist/api/get-entity-tool-api.js +2 -0
- package/dist/api/guest-management-tool-api.js +75 -0
- package/dist/api/location-tool-api.js +48 -1
- package/dist/api/lpr-tool-api.js +26 -0
- package/dist/api/policy-alerts-tool-api.js +54 -0
- package/dist/api/report-tool-api.js +345 -23
- package/dist/api/rules-tool-api.js +78 -0
- package/dist/api/search-tool-api.js +93 -0
- package/dist/api/time-tool-api.js +3 -1
- package/dist/api/update-tool-api.js +204 -0
- package/dist/api/user-access-trail-tool-api.js +45 -0
- package/dist/api/user-audit-tool-api.js +47 -0
- package/dist/api/user-tool-api.js +77 -0
- package/dist/createServer.js +3 -1
- package/dist/filtering-utils.js +298 -0
- package/dist/index.js +0 -9
- package/dist/logger.js +6 -5
- package/dist/network.js +18 -4
- package/dist/tools/access-control-tool.js +97 -0
- package/dist/tools/alarm-monitoring-tool.js +50 -0
- package/dist/tools/analytics-tool.js +383 -0
- package/dist/tools/camera-tool.js +27 -9
- package/dist/tools/camera-uptime-tool.js +50 -0
- package/dist/tools/clips-tool.js +46 -16
- package/dist/tools/door-tool.js +66 -0
- package/dist/tools/events-tool.js +103 -81
- package/dist/tools/faces-tool.js +175 -32
- package/dist/tools/get-entity-tool.js +6 -1
- package/dist/tools/guest-management-tool.js +50 -0
- package/dist/tools/location-tool.js +23 -4
- package/dist/tools/lpr-tool.js +26 -20
- package/dist/tools/policy-alerts-tool.js +34 -4
- package/dist/tools/report-tool.js +181 -45
- package/dist/tools/rules-tool.js +76 -0
- package/dist/tools/search-tool.js +69 -0
- package/dist/tools/update-tool.js +63 -2
- package/dist/tools/user-access-trail-tool.js +64 -0
- package/dist/tools/user-audit-tool.js +53 -0
- package/dist/tools/user-tool.js +55 -0
- package/dist/tools/video-walls-tool.js +41 -0
- package/dist/transports/streamable-http.js +25 -15
- package/dist/types/access-control-tool-types.js +120 -0
- package/dist/types/alarm-monitoring-tool-types.js +60 -0
- package/dist/types/analytics-tool-types.js +190 -0
- package/dist/types/camera-tool-types.js +3 -2
- package/dist/types/camera-uptime-tool-types.js +52 -0
- package/dist/types/clips-tool-types.js +60 -11
- package/dist/types/door-tool-types.js +64 -0
- package/dist/types/events-tools-types.js +71 -3
- package/dist/types/faces-tools-types.js +36 -0
- package/dist/types/guest-management-tool-types.js +57 -0
- package/dist/types/location-tool-types.js +10 -1
- package/dist/types/lpr-tool-types.js +14 -0
- package/dist/types/policy-alerts-tool-types.js +38 -3
- package/dist/types/report-tool-types.js +284 -42
- package/dist/types/rules-tool-types.js +58 -0
- package/dist/types/schema.js +3123 -1
- package/dist/types/search-tool-types.js +82 -0
- package/dist/types/update-tool-types.js +4 -1
- package/dist/types/user-access-trail-tool-types.js +54 -0
- package/dist/types/user-audit-tool-types.js +48 -0
- package/dist/types/user-tool-types.js +61 -0
- package/dist/types/video-walls-tool-types.js +59 -0
- package/dist/types/zod-schemas.js +3237 -986
- package/dist/types.js +0 -21
- package/dist/util.js +34 -57
- package/package.json +7 -5
- package/dist/disabled-tools/semantic-search-tool.js +0 -90
- package/dist/tools/create-tool.js +0 -30
- package/dist/types/create-tool-types.js +0 -8
- package/dist/types/schema-components.js +0 -7211
- package/dist/types/semantic-search-tool-types.js +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getHumanMotionEvents, } from "../api/events-tool-api.js";
|
|
1
|
+
import { getAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getHumanMotionEvents, getButtonPressEvents, getOccupancyEvents, getProximityEvents, getDoorbellEvents, } from "../api/events-tool-api.js";
|
|
2
2
|
import { EventsToolRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/events-tools-types.js";
|
|
3
3
|
import { createToolStructuredContent } from "../util.js";
|
|
4
4
|
import { getLogger } from "../logger.js";
|
|
@@ -7,56 +7,59 @@ const logger = getLogger("events-tool");
|
|
|
7
7
|
const TOOL_NAME = "events-tool";
|
|
8
8
|
// "faces" | "people" | "human" | "access-control"
|
|
9
9
|
const TOOL_DESCRIPTION = `
|
|
10
|
-
This tool
|
|
10
|
+
**Scope:** This tool returns **raw, event-level data** (individual events with timestamps). Use **report-tool** when you need aggregated counts, time-series summaries, or analytics over intervals.
|
|
11
11
|
|
|
12
|
-
This tool
|
|
12
|
+
This tool has 5 modes, set by "eventType": access-control, environmental-gateway, climate-sensor, component-events, camera. Use it when the user asks for specific events (unlocks, badge ins, credentials, arrivals, environmental readings, climate data, camera motion, or other component events). It can return large result sets; keep time ranges narrow. For ranges spanning more than ~24 hours, prefer report-tool for aggregates. For maximum flexibility across event types at a location, use eventType "component-events".
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
---
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
When eventType is "access-control":
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
This tool takes 3 arguments:
|
|
20
|
-
* **accessControlledDoorUuid (string):** The unique identifier for the access controlled door.
|
|
21
|
-
* **startTime (string):** The timestamp (in ISO 8601 format) representing the start or earliest time of access control events.
|
|
22
|
-
* **endTime (string):** The timestamp (in ISO 8601 format) representing the end or latest time of access control events.
|
|
18
|
+
Retrieves access control events (arrivals, badge ins, credentials, unlocks) for the given door(s). Can return a lot of data—use a narrow time range.
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
Arguments:
|
|
21
|
+
* **accessControlledDoorUuids (array of strings):** UUIDs of the access-controlled doors.
|
|
22
|
+
* **startTime (string):** Start of the time range (ISO 8601).
|
|
23
|
+
* **endTime (string):** End of the time range (ISO 8601).
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
The \`credSource\` field indicates how the event was triggered:
|
|
26
|
+
* **REMOTE:** Rhombus Key app remote unlock.
|
|
27
|
+
* **REMOTE (Admin):** Unlock via Rhombus console or browser/mobile app.
|
|
28
|
+
* **BLE_WAVE:** User waved hand over the reader.
|
|
29
|
+
* **NFC:** User tapped badge or phone on the reader.
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
the timezone of the **device**, not necessarily UTC time.
|
|
31
|
+
---
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
* **deviceUuid (string):** The unique identifier for the environmental gateway device.
|
|
33
|
-
* **startTime (string):** The timestamp (in ISO 8601 format) representing the start time of events.
|
|
34
|
-
* **endTime (string):** The timestamp (in ISO 8601 format) representing the end time of events.
|
|
33
|
+
Retrieves environmental gateway events (sensor readings, derived values) for a device in a time range. Timestamps are in the **device** timezone, not necessarily UTC.
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
Arguments:
|
|
36
|
+
* **deviceUuid (string):** UUID of the environmental gateway device.
|
|
37
|
+
* **startTime (string):** Start of range (ISO 8601).
|
|
38
|
+
* **endTime (string):** End of range (ISO 8601).
|
|
39
|
+
|
|
40
|
+
---
|
|
37
41
|
|
|
38
42
|
When eventType is "climate-sensor":
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
the timezone of the **sensor**, not necessarily UTC time.
|
|
44
|
+
Retrieves climate sensor events (temperature, humidity, air quality, etc.) for a sensor in a time range. Timestamps are in the **sensor** timezone, not necessarily UTC.
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
* **sensorUuid (string):**
|
|
45
|
-
* **startTime (string):**
|
|
46
|
-
* **endTime (string):**
|
|
47
|
-
* **limit (number, optional):**
|
|
46
|
+
Arguments:
|
|
47
|
+
* **sensorUuid (string):** UUID of the climate sensor.
|
|
48
|
+
* **startTime (string):** Start of range (ISO 8601).
|
|
49
|
+
* **endTime (string):** End of range (ISO 8601).
|
|
50
|
+
* **limit (number, optional):** Max events to return. Default 1000.
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
---
|
|
50
53
|
|
|
51
54
|
When eventType is "component-events":
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
Retrieves all component event types for a location in a time range. Most flexible option; filter by event type via componentEventTypes. Timestamps are in the **location** timezone, not necessarily UTC.
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
* **locationUuid (string):**
|
|
57
|
-
* **componentEventTypes (array
|
|
58
|
-
* **startTime (string):**
|
|
59
|
-
* **endTime (string):**
|
|
58
|
+
Arguments:
|
|
59
|
+
* **locationUuid (string):** UUID of the location.
|
|
60
|
+
* **componentEventTypes (array, optional):** Event types to include. If empty or omitted, returns all types.
|
|
61
|
+
* **startTime (string):** Start of range (ISO 8601).
|
|
62
|
+
* **endTime (string):** End of range (ISO 8601).
|
|
60
63
|
|
|
61
64
|
Valid event types include:
|
|
62
65
|
* **DoorbellEvent:** Doorbell button press events
|
|
@@ -71,108 +74,127 @@ Valid event types include:
|
|
|
71
74
|
* **WaveToUnlockIntentExpiredEvent:** Wave-to-unlock timeout events
|
|
72
75
|
* **DoorAuthFirstInStateEvent:** First-in authentication state events
|
|
73
76
|
* **DoorScheduleFirstInStateEvent:** First-in schedule state events
|
|
74
|
-
* And more
|
|
77
|
+
* And more (see input schema for full list).
|
|
78
|
+
|
|
79
|
+
---
|
|
75
80
|
|
|
76
81
|
When eventType is "camera":
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
Retrieves human motion events for a camera in a time range. Timestamps in milliseconds.
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
* **cameraUuid (string):**
|
|
82
|
-
* **startTime (string):**
|
|
83
|
-
* **duration (number):**
|
|
85
|
+
Arguments:
|
|
86
|
+
* **cameraUuid (string):** UUID of the camera.
|
|
87
|
+
* **startTime (string):** Start of range (ISO 8601).
|
|
88
|
+
* **duration (number):** Search window in seconds. Default 3600 (1 hour).
|
|
84
89
|
|
|
85
|
-
The tool returns a JSON object with camera events data.
|
|
86
90
|
`;
|
|
87
91
|
const TOOL_HANDLER = async (args, extra) => {
|
|
88
|
-
const { eventType, accessControlledDoorUuids, deviceUuid, sensorUuid, locationUuid, componentEventTypes, startTime, endTime, limit, timeZone, tempUnit, cameraUuid, duration, } = args;
|
|
92
|
+
const { eventType, accessControlledDoorUuids, deviceUuid, sensorUuid, locationUuid, componentEventTypes, startTime, endTime, limit, timeZone, tempUnit, cameraUuid, duration, buttonSensorUuid, occupancySensorUuid, proximityTagUuids, doorbellCameraUuid, } = args;
|
|
89
93
|
logger.debug(`eventType: ${eventType}`);
|
|
90
94
|
switch (eventType) {
|
|
91
95
|
case "access-control": {
|
|
92
96
|
if (!accessControlledDoorUuids || accessControlledDoorUuids.length === 0) {
|
|
93
|
-
|
|
97
|
+
return createToolStructuredContent({
|
|
94
98
|
needUserInput: true,
|
|
95
99
|
commandForUser: "Which door are you asking about?",
|
|
96
|
-
};
|
|
97
|
-
return createToolStructuredContent(result);
|
|
100
|
+
});
|
|
98
101
|
}
|
|
99
102
|
else {
|
|
100
103
|
const events = await getAccessControlEvents(accessControlledDoorUuids, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
101
|
-
|
|
102
|
-
eventType: "access-control",
|
|
103
|
-
accessControlEvents: events,
|
|
104
|
-
};
|
|
105
|
-
return createToolStructuredContent(result);
|
|
104
|
+
return createToolStructuredContent({ eventType: "access-control", accessControlEvents: events });
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
case "environmental-gateway": {
|
|
109
108
|
if (!deviceUuid) {
|
|
110
|
-
|
|
109
|
+
return createToolStructuredContent({
|
|
111
110
|
needUserInput: true,
|
|
112
111
|
commandForUser: "Which environmental gateway device are you asking about?",
|
|
113
|
-
};
|
|
114
|
-
return createToolStructuredContent(result);
|
|
112
|
+
});
|
|
115
113
|
}
|
|
116
114
|
else {
|
|
117
115
|
const events = await getEventsForEnvironmentalGateway(deviceUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, tempUnit ?? TempUnit.CELSIUS, extra._meta?.requestModifiers, extra.sessionId);
|
|
118
|
-
|
|
119
|
-
eventType: "environmental-gateway",
|
|
120
|
-
environmentalGatewayEvents: events,
|
|
121
|
-
};
|
|
122
|
-
return createToolStructuredContent(result);
|
|
116
|
+
return createToolStructuredContent({ eventType: "environmental-gateway", environmentalGatewayEvents: events });
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
case "climate-sensor": {
|
|
126
120
|
if (!sensorUuid) {
|
|
127
|
-
|
|
121
|
+
return createToolStructuredContent({
|
|
128
122
|
needUserInput: true,
|
|
129
123
|
commandForUser: "Which climate sensor are you asking about?",
|
|
130
|
-
};
|
|
131
|
-
return createToolStructuredContent(result);
|
|
124
|
+
});
|
|
132
125
|
}
|
|
133
126
|
else {
|
|
134
127
|
const events = await getClimateEventsForSensor(sensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, limit ?? null, timeZone, tempUnit ?? TempUnit.CELSIUS, extra._meta?.requestModifiers, extra.sessionId);
|
|
135
|
-
|
|
136
|
-
eventType: "climate-sensor",
|
|
137
|
-
climateSensorEvents: events,
|
|
138
|
-
};
|
|
139
|
-
return createToolStructuredContent(result);
|
|
128
|
+
return createToolStructuredContent({ eventType: "climate-sensor", climateSensorEvents: events });
|
|
140
129
|
}
|
|
141
130
|
}
|
|
142
131
|
case "component-events": {
|
|
143
132
|
if (!locationUuid) {
|
|
144
|
-
|
|
133
|
+
return createToolStructuredContent({
|
|
145
134
|
needUserInput: true,
|
|
146
135
|
commandForUser: "Which location are you asking about?",
|
|
147
|
-
};
|
|
148
|
-
return createToolStructuredContent(result);
|
|
136
|
+
});
|
|
149
137
|
}
|
|
150
138
|
else {
|
|
151
139
|
const events = await getComponentEventsByLocation(locationUuid, componentEventTypes || [], startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
152
|
-
|
|
153
|
-
eventType: "component-events",
|
|
154
|
-
componentEvents: events,
|
|
155
|
-
};
|
|
156
|
-
return createToolStructuredContent(result);
|
|
140
|
+
return createToolStructuredContent({ eventType: "component-events", componentEvents: events });
|
|
157
141
|
}
|
|
158
142
|
}
|
|
159
143
|
case EventsToolRequestType.CAMERA: {
|
|
160
144
|
if (!cameraUuid) {
|
|
161
|
-
|
|
145
|
+
return createToolStructuredContent({
|
|
162
146
|
needUserInput: true,
|
|
163
147
|
commandForUser: "Which camera are you asking about?",
|
|
164
|
-
};
|
|
165
|
-
return createToolStructuredContent(result);
|
|
148
|
+
});
|
|
166
149
|
}
|
|
167
150
|
else {
|
|
168
|
-
const events = await getHumanMotionEvents(cameraUuid, duration ?? 3600,
|
|
169
|
-
|
|
170
|
-
|
|
151
|
+
const events = await getHumanMotionEvents(cameraUuid, duration ?? 3600, startTime ? new Date(startTime).getTime() : Date.now() - 3600000, extra._meta?.requestModifiers, extra.sessionId);
|
|
152
|
+
return createToolStructuredContent({ eventType: "camera", cameraEvents: events.uniqueHumanEvents });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
case EventsToolRequestType.BUTTON_PRESS: {
|
|
156
|
+
const bSensorUuid = args.buttonSensorUuid;
|
|
157
|
+
if (!bSensorUuid) {
|
|
158
|
+
return createToolStructuredContent({
|
|
159
|
+
needUserInput: true,
|
|
160
|
+
commandForUser: "Which button sensor are you asking about?",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const buttonEvents = await getButtonPressEvents(bSensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
164
|
+
return createToolStructuredContent({ eventType: "button-press", buttonPressEvents: buttonEvents });
|
|
165
|
+
}
|
|
166
|
+
case EventsToolRequestType.OCCUPANCY: {
|
|
167
|
+
const occSensorUuid = args.occupancySensorUuid;
|
|
168
|
+
if (!occSensorUuid) {
|
|
171
169
|
return createToolStructuredContent({
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
needUserInput: true,
|
|
171
|
+
commandForUser: "Which occupancy sensor are you asking about?",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const occupancyEvts = await getOccupancyEvents(occSensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
175
|
+
return createToolStructuredContent({ eventType: "occupancy", occupancyEvents: occupancyEvts });
|
|
176
|
+
}
|
|
177
|
+
case EventsToolRequestType.PROXIMITY: {
|
|
178
|
+
const tagUuids = args.proximityTagUuids;
|
|
179
|
+
if (!tagUuids || tagUuids.length === 0) {
|
|
180
|
+
return createToolStructuredContent({
|
|
181
|
+
needUserInput: true,
|
|
182
|
+
commandForUser: "Which proximity tags are you asking about?",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const proxEvents = await getProximityEvents(tagUuids, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
186
|
+
return createToolStructuredContent({ eventType: "proximity", proximityEvents: proxEvents });
|
|
187
|
+
}
|
|
188
|
+
case EventsToolRequestType.DOORBELL: {
|
|
189
|
+
const dbCamUuid = args.doorbellCameraUuid;
|
|
190
|
+
if (!dbCamUuid) {
|
|
191
|
+
return createToolStructuredContent({
|
|
192
|
+
needUserInput: true,
|
|
193
|
+
commandForUser: "Which doorbell camera are you asking about?",
|
|
174
194
|
});
|
|
175
195
|
}
|
|
196
|
+
const doorbellEvts = await getDoorbellEvents(dbCamUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
|
|
197
|
+
return createToolStructuredContent({ eventType: "doorbell", doorbellEvents: doorbellEvts });
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
// This should not happen, but return empty result if eventType is unknown
|
package/dist/tools/faces-tool.js
CHANGED
|
@@ -1,61 +1,204 @@
|
|
|
1
|
-
import { getFaceEvents, getRegisteredFaces } from "../api/faces-tool-api.js";
|
|
1
|
+
import { getFaceEvents, getFaceEventsByPerson, getFaceMatchmakers, getPersonLabels, getRegisteredFaces, searchSimilarFaces } from "../api/faces-tool-api.js";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
2
3
|
import { OUTPUT_SCHEMA, RequestType, TOOL_ARGS, } from "../types/faces-tools-types.js";
|
|
3
|
-
import { extractFromToolExtra } from "../util.js";
|
|
4
|
+
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
5
|
const TOOL_NAME = "faces-tool";
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Fuzzy-match user-provided names against the registered faces directory.
|
|
8
|
+
* Scoring: 4 = exact full name, 3 = first name, 2 = last name, 1 = substring (min 3 chars).
|
|
9
|
+
* Returns the best match per queried name.
|
|
10
|
+
*/
|
|
11
|
+
function resolveNamesToRegisteredFaces(userNames, registeredPeople) {
|
|
12
|
+
return userNames.map(userName => {
|
|
13
|
+
const input = userName.toLowerCase().trim();
|
|
14
|
+
if (!input)
|
|
15
|
+
return { queriedName: userName, resolvedName: null, resolvedPersonUuid: null };
|
|
16
|
+
let best = null;
|
|
17
|
+
for (const person of registeredPeople) {
|
|
18
|
+
if (!person.name || !person.uuid)
|
|
19
|
+
continue;
|
|
20
|
+
const fullName = person.name.toLowerCase().trim();
|
|
21
|
+
const parts = fullName.split(/\s+/);
|
|
22
|
+
let score = 0;
|
|
23
|
+
if (fullName === input)
|
|
24
|
+
score = 4;
|
|
25
|
+
else if (parts[0] === input)
|
|
26
|
+
score = 3;
|
|
27
|
+
else if (parts.length > 1 && parts[parts.length - 1] === input)
|
|
28
|
+
score = 2;
|
|
29
|
+
else if (input.length >= 3 && fullName.includes(input))
|
|
30
|
+
score = 1;
|
|
31
|
+
if (score > 0 && (!best || score > best.score)) {
|
|
32
|
+
best = { name: person.name, uuid: person.uuid, score };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
queriedName: userName,
|
|
37
|
+
resolvedName: best?.name ?? null,
|
|
38
|
+
resolvedPersonUuid: best?.uuid ?? null,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
6
42
|
const TOOL_DESCRIPTION = `
|
|
7
|
-
This tool interacts with the Rhombus face recognition system to retrieve information about face sightings and registered faces.
|
|
43
|
+
This tool interacts with the Rhombus face recognition system to retrieve information about face sightings and registered faces.
|
|
8
44
|
|
|
9
|
-
|
|
45
|
+
If the user is asking about how many people were seen (head count / occupancy), use the report-tool with GET_OCCUPANCY_ENABLED_CAMERAS and GET_OCCUPANCY_COUNT_REPORT instead. This tool (faces-tool) is best for identifying *who* was seen (unique individuals by name), and its face count data is also automatically included in report-tool people-counting responses via the faceCountEnrichment field.
|
|
46
|
+
|
|
47
|
+
**Important for person-presence questions:** When asked whether specific people were seen or are present, you should ALSO call events-tool with eventType "access-control" to check badge-in records. Face recognition and access control are complementary — someone may badge in without face recognition triggering, or be seen by a camera without badging in.
|
|
10
48
|
|
|
11
49
|
If the requestType is "get-face-events":
|
|
12
|
-
- Use this tool to answer questions about face sightings, including questions like "who was in the office" or "who was seen today".
|
|
13
|
-
-
|
|
50
|
+
- Use this tool to answer questions about face sightings, including questions like "who was in the office" or "who was seen today". Can be used for reporting, to generate a report on who was seen by the camera system.
|
|
51
|
+
- **Automatic name resolution:** You can pass partial or first-name-only names in faceNames (e.g., "Brandon", "Omar"). The tool automatically looks up the registered faces directory and resolves them to exact names and person UUIDs before searching. Check the "resolvedNames" field in the response to see what each queried name was matched to (null means no match found).
|
|
14
52
|
- You can filter face events using parameters like 'faceNames', 'hasEmbedding', 'hasName', 'labels', 'locationUuids', 'personUuids', and a time range using 'rangeStart' and 'rangeEnd' (timestamps in milliseconds).
|
|
15
53
|
- If you'd like to know about all face events at a location, pass in a location UUID and no device UUIDs. This will correctly return all face events at that location.
|
|
54
|
+
- When the user asks about a specific person at a location (e.g. "Jane Doe at Main Office"), call get-registered-faces first to get the list of registered names, find the best match, then call get-face-events with that precise name. The tool expects precise names as stored in the system.
|
|
55
|
+
- When querying faces at a location, pass only the location UUID in searchFilter; do not pass device UUIDs in searchFilter.deviceUuids, so the API returns all faces detected at that location.
|
|
16
56
|
|
|
17
57
|
If the requestType is "get-registered-faces":
|
|
18
|
-
- 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.
|
|
19
|
-
-
|
|
20
|
-
-
|
|
58
|
+
- 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, including their assigned labels.
|
|
59
|
+
- This returns ALL people registered in the system, regardless of the provided timestampFilter.
|
|
60
|
+
- Each person in the response includes a "labels" array showing which label groups they belong to (e.g., "Engineering", "Visitors"). Use these labels to answer questions about groups of people.
|
|
61
|
+
|
|
62
|
+
If the requestType is "get-person-labels":
|
|
63
|
+
- This retrieves a mapping of all person UUIDs to their assigned labels across the organization.
|
|
64
|
+
- Use this to discover what label groups exist and which registered faces belong to each group.
|
|
65
|
+
- Useful when the user asks about a group (e.g., "was anyone from Engineering seen today?") — get the labels first, find the person UUIDs for that label, then query face events filtered by those personUuids or labels.
|
|
21
66
|
`;
|
|
22
67
|
const TOOL_HANDLER = async (args, extra) => {
|
|
23
68
|
const { requestModifiers, sessionId } = extractFromToolExtra(extra);
|
|
24
|
-
let ret = {
|
|
25
|
-
requestType: args.requestType,
|
|
26
|
-
};
|
|
27
69
|
if (args.requestType === RequestType.GET_FACE_EVENTS) {
|
|
28
|
-
const
|
|
29
|
-
|
|
70
|
+
const faceEventArgs = args.faceEventFilter;
|
|
71
|
+
let resolvedNamesOutput;
|
|
72
|
+
const providedNames = faceEventArgs.searchFilter?.faceNames ?? [];
|
|
73
|
+
if (providedNames.length > 0 && faceEventArgs.searchFilter) {
|
|
74
|
+
try {
|
|
75
|
+
const peopleResponse = await getRegisteredFaces({}, requestModifiers, sessionId);
|
|
76
|
+
if (peopleResponse.people) {
|
|
77
|
+
const resolutions = resolveNamesToRegisteredFaces(providedNames, peopleResponse.people);
|
|
78
|
+
resolvedNamesOutput = {};
|
|
79
|
+
const resolvedUuids = [];
|
|
80
|
+
const resolvedExactNames = [];
|
|
81
|
+
const unresolvedNames = [];
|
|
82
|
+
for (const r of resolutions) {
|
|
83
|
+
resolvedNamesOutput[r.queriedName] = r.resolvedName;
|
|
84
|
+
if (r.resolvedPersonUuid && r.resolvedName) {
|
|
85
|
+
resolvedUuids.push(r.resolvedPersonUuid);
|
|
86
|
+
resolvedExactNames.push(r.resolvedName);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
unresolvedNames.push(r.queriedName);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (resolvedUuids.length > 0) {
|
|
93
|
+
const existingUuids = faceEventArgs.searchFilter.personUuids ?? [];
|
|
94
|
+
const mergedUuids = [...new Set([...existingUuids, ...resolvedUuids])];
|
|
95
|
+
faceEventArgs.searchFilter.personUuids = mergedUuids;
|
|
96
|
+
faceEventArgs.searchFilter.faceNames = [...resolvedExactNames, ...unresolvedNames];
|
|
97
|
+
logger.info(`[faces-tool] Auto-resolved names: ${JSON.stringify(resolvedNamesOutput)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
logger.warn("[faces-tool] Name resolution failed, proceeding with original args", e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let { faceEvents, lastEvaluatedKey } = await getFaceEvents(faceEventArgs, args.timeZone, requestModifiers, sessionId);
|
|
106
|
+
const hadLocationFilter = faceEventArgs.searchFilter?.locationUuids &&
|
|
107
|
+
faceEventArgs.searchFilter.locationUuids.length > 0;
|
|
108
|
+
if (faceEvents.length === 0 && hadLocationFilter) {
|
|
109
|
+
logger.info(`[faces-tool] Empty results with locationUuids filter ${JSON.stringify(faceEventArgs.searchFilter.locationUuids)}, retrying without location filter`);
|
|
110
|
+
const retryArgs = {
|
|
111
|
+
...faceEventArgs,
|
|
112
|
+
searchFilter: { ...faceEventArgs.searchFilter, locationUuids: [] },
|
|
113
|
+
};
|
|
114
|
+
const retry = await getFaceEvents(retryArgs, args.timeZone, requestModifiers, sessionId);
|
|
115
|
+
faceEvents = retry.faceEvents;
|
|
116
|
+
lastEvaluatedKey = retry.lastEvaluatedKey;
|
|
117
|
+
}
|
|
118
|
+
return createToolStructuredContent({
|
|
30
119
|
requestType: RequestType.GET_FACE_EVENTS,
|
|
31
|
-
getFaceEventsResponse:
|
|
32
|
-
|
|
120
|
+
getFaceEventsResponse: faceEvents,
|
|
121
|
+
lastEvaluatedKey: lastEvaluatedKey ?? undefined,
|
|
122
|
+
resolvedNames: resolvedNamesOutput ?? undefined,
|
|
123
|
+
});
|
|
33
124
|
}
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
125
|
+
if (args.requestType === RequestType.GET_REGISTERED_FACES) {
|
|
126
|
+
const [peopleResponse, labelsResponse] = await Promise.all([
|
|
127
|
+
getRegisteredFaces(args, requestModifiers, sessionId),
|
|
128
|
+
getPersonLabels(requestModifiers, sessionId),
|
|
129
|
+
]);
|
|
130
|
+
const labelsByPerson = labelsResponse.labelsByPerson ?? {};
|
|
131
|
+
if (peopleResponse.people) {
|
|
132
|
+
return createToolStructuredContent({
|
|
38
133
|
requestType: RequestType.GET_REGISTERED_FACES,
|
|
39
|
-
getSavedFacesResponse:
|
|
134
|
+
getSavedFacesResponse: peopleResponse.people.map(p => ({
|
|
40
135
|
createdOn: p.createdOn ? parseInt(p.createdOn, 10) : undefined,
|
|
41
136
|
name: p.name ?? undefined,
|
|
42
137
|
orgUuid: p.orgUuid ?? undefined,
|
|
43
138
|
updatedOn: p.updatedOn ? parseInt(p.updatedOn, 10) : undefined,
|
|
44
139
|
uuid: p.uuid ?? undefined,
|
|
140
|
+
labels: p.uuid
|
|
141
|
+
? (labelsByPerson[p.uuid] ?? []).filter((l) => l != null)
|
|
142
|
+
: undefined,
|
|
45
143
|
})),
|
|
46
|
-
};
|
|
144
|
+
});
|
|
47
145
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
146
|
+
return createToolStructuredContent({
|
|
147
|
+
requestType: RequestType.GET_REGISTERED_FACES,
|
|
148
|
+
error: String(peopleResponse.error),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (args.requestType === RequestType.GET_PERSON_LABELS) {
|
|
152
|
+
const response = await getPersonLabels(requestModifiers, sessionId);
|
|
153
|
+
const cleaned = {};
|
|
154
|
+
if (response.labelsByPerson) {
|
|
155
|
+
for (const [personUuid, labels] of Object.entries(response.labelsByPerson)) {
|
|
156
|
+
if (labels) {
|
|
157
|
+
cleaned[personUuid] = labels.filter((l) => l != null);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return createToolStructuredContent({
|
|
162
|
+
requestType: RequestType.GET_PERSON_LABELS,
|
|
163
|
+
getPersonLabelsResponse: cleaned,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (args.requestType === RequestType.SEARCH_SIMILAR_FACES) {
|
|
167
|
+
if (!args.faceEventUuid) {
|
|
168
|
+
return createToolStructuredContent({
|
|
169
|
+
requestType: RequestType.SEARCH_SIMILAR_FACES,
|
|
170
|
+
error: "faceEventUuid is required for search-similar-faces",
|
|
171
|
+
});
|
|
53
172
|
}
|
|
173
|
+
const similarEvents = await searchSimilarFaces(args.faceEventUuid, args.timeZone, requestModifiers, sessionId);
|
|
174
|
+
return createToolStructuredContent({
|
|
175
|
+
requestType: RequestType.SEARCH_SIMILAR_FACES,
|
|
176
|
+
similarFaceEvents: similarEvents,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (args.requestType === RequestType.GET_FACE_MATCHMAKERS) {
|
|
180
|
+
const matchmakers = await getFaceMatchmakers(requestModifiers, sessionId);
|
|
181
|
+
return createToolStructuredContent({
|
|
182
|
+
requestType: RequestType.GET_FACE_MATCHMAKERS,
|
|
183
|
+
faceMatchmakers: matchmakers,
|
|
184
|
+
});
|
|
54
185
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
186
|
+
if (args.requestType === RequestType.GET_FACE_EVENTS_BY_PERSON) {
|
|
187
|
+
if (!args.personUuid) {
|
|
188
|
+
return createToolStructuredContent({
|
|
189
|
+
requestType: RequestType.GET_FACE_EVENTS_BY_PERSON,
|
|
190
|
+
error: "personUuid is required for get-face-events-by-person",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const { faceEvents: personEvents } = await getFaceEventsByPerson(args.personUuid, args.timeZone, requestModifiers, sessionId);
|
|
194
|
+
return createToolStructuredContent({
|
|
195
|
+
requestType: RequestType.GET_FACE_EVENTS_BY_PERSON,
|
|
196
|
+
personFaceEvents: personEvents,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return createToolStructuredContent({
|
|
200
|
+
requestType: args.requestType,
|
|
201
|
+
});
|
|
59
202
|
};
|
|
60
203
|
export function createTool(server) {
|
|
61
204
|
server.registerTool(TOOL_NAME, {
|
|
@@ -7,7 +7,12 @@ const TOOL_DESCRIPTION = `
|
|
|
7
7
|
Retrieves entities (or devices) of certain types.
|
|
8
8
|
Can request multiple entity types at once.
|
|
9
9
|
The return structure is a JSON string that contains the states of the requested entities.
|
|
10
|
-
This data is exact. Whatever entities exist will be returned here
|
|
10
|
+
This data is exact. Whatever entities exist will be returned here.
|
|
11
|
+
|
|
12
|
+
This is the primary tool for checking device health and connectivity status. Each device in the response
|
|
13
|
+
includes a "connected" boolean field indicating whether it is currently online (true) or offline (false).
|
|
14
|
+
When asked about device health, offline devices, or connectivity issues, use this tool to fetch all device
|
|
15
|
+
types and check the "connected" field to identify which devices are offline or unreachable.`;
|
|
11
16
|
const TOOL_HANDLER = async (args, extra) => {
|
|
12
17
|
const { entityTypes, timeZone, filterBy, tempUnit } = args;
|
|
13
18
|
const { requestModifiers, sessionId } = extractFromToolExtra(extra);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getAllGuestsByOrg, getGuestActivityLogs, getActivitiesForLocation, } from "../api/guest-management-tool-api.js";
|
|
2
|
+
import { GuestManagementRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/guest-management-tool-types.js";
|
|
3
|
+
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
|
+
const TOOL_NAME = "guest-management-tool";
|
|
5
|
+
const TOOL_DESCRIPTION = `
|
|
6
|
+
This tool manages Rhombus guest/visitor operations. It can list all guests, retrieve activity logs, and filter activities by location.
|
|
7
|
+
|
|
8
|
+
It has the following modes of operation, determined by the "requestType" parameter:
|
|
9
|
+
- ${GuestManagementRequestType.GET_ALL_GUESTS}: List all guests across the organization with their check-in status, host, company, and contact info.
|
|
10
|
+
- ${GuestManagementRequestType.GET_ACTIVITY_LOGS}: Get org-wide guest activity logs (sign-ins, sign-outs). Supports optional time range filtering via startTimeMs/endTimeMs.
|
|
11
|
+
- ${GuestManagementRequestType.GET_ACTIVITIES_FOR_LOCATION}: Get guest activity logs for a specific location. Requires locationUuid. Supports optional time range filtering.
|
|
12
|
+
`;
|
|
13
|
+
const TOOL_HANDLER = async (args, _extra) => {
|
|
14
|
+
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
15
|
+
try {
|
|
16
|
+
switch (args.requestType) {
|
|
17
|
+
case GuestManagementRequestType.GET_ALL_GUESTS: {
|
|
18
|
+
const guests = await getAllGuestsByOrg(requestModifiers, sessionId);
|
|
19
|
+
return createToolStructuredContent({ guests });
|
|
20
|
+
}
|
|
21
|
+
case GuestManagementRequestType.GET_ACTIVITY_LOGS: {
|
|
22
|
+
const activities = await getGuestActivityLogs(args.startTimeMs ?? undefined, args.endTimeMs ?? undefined, requestModifiers, sessionId);
|
|
23
|
+
return createToolStructuredContent({ activities });
|
|
24
|
+
}
|
|
25
|
+
case GuestManagementRequestType.GET_ACTIVITIES_FOR_LOCATION: {
|
|
26
|
+
if (!args.locationUuid) {
|
|
27
|
+
return createToolStructuredContent({
|
|
28
|
+
error: "locationUuid is required for get-activities-for-location.",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const activities = await getActivitiesForLocation(args.locationUuid, args.startTimeMs ?? undefined, args.endTimeMs ?? undefined, requestModifiers, sessionId);
|
|
32
|
+
return createToolStructuredContent({ activities });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof Error) {
|
|
38
|
+
return createToolStructuredContent({ error: error.message });
|
|
39
|
+
}
|
|
40
|
+
return createToolStructuredContent({ error: "Unknown error" });
|
|
41
|
+
}
|
|
42
|
+
return createToolStructuredContent({ error: "Invalid request type" });
|
|
43
|
+
};
|
|
44
|
+
export function createTool(server) {
|
|
45
|
+
server.registerTool(TOOL_NAME, {
|
|
46
|
+
description: TOOL_DESCRIPTION,
|
|
47
|
+
inputSchema: TOOL_ARGS,
|
|
48
|
+
outputSchema: OUTPUT_SCHEMA.shape,
|
|
49
|
+
}, TOOL_HANDLER);
|
|
50
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { getLocations } from "../api/location-tool-api.js";
|
|
1
|
+
import { getLocations, createLocation, updateLocation, getLocationLabels } from "../api/location-tool-api.js";
|
|
2
2
|
import { TOOL_ARGS } from "../types/location-tool-types.js";
|
|
3
3
|
const TOOL_NAME = "location-tool";
|
|
4
4
|
const TOOL_DESCRIPTION = `This tool performs operations on locations.
|
|
5
|
-
- 'get': Retrieves all locations. When generating reports with location details, use location names not uuids
|
|
5
|
+
- 'get': Retrieves all locations. When generating reports with location details, use location names not uuids.
|
|
6
|
+
- 'create': Creates a new location with a name and optional address.
|
|
7
|
+
- 'update': Updates an existing location (requires locationUuid).
|
|
8
|
+
- 'get-labels': Retrieves all location labels for the organization.`;
|
|
6
9
|
const TOOL_HANDLER = async (args, extra) => {
|
|
7
10
|
const { action } = args;
|
|
8
11
|
let ret;
|
|
@@ -10,8 +13,24 @@ const TOOL_HANDLER = async (args, extra) => {
|
|
|
10
13
|
case "get":
|
|
11
14
|
ret = await getLocations(extra._meta?.requestModifiers, extra.sessionId);
|
|
12
15
|
break;
|
|
13
|
-
case "
|
|
14
|
-
|
|
16
|
+
case "create": {
|
|
17
|
+
if (!args.locationName) {
|
|
18
|
+
ret = { error: true, status: "locationName is required for 'create'" };
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
ret = await createLocation(args.locationName, args.locationAddress ?? undefined, extra._meta?.requestModifiers, extra.sessionId);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case "update": {
|
|
25
|
+
if (!args.locationUuid) {
|
|
26
|
+
ret = { error: true, status: "locationUuid is required for 'update'" };
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
ret = await updateLocation(args.locationUuid, args.locationUpdate?.name, args.locationAddress ?? undefined, extra._meta?.requestModifiers, extra.sessionId);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "get-labels":
|
|
33
|
+
ret = await getLocationLabels(extra._meta?.requestModifiers, extra.sessionId);
|
|
15
34
|
break;
|
|
16
35
|
default:
|
|
17
36
|
ret = { error: true, status: `unsupported location tool call: ${action}` };
|