rhombus-node-mcp 0.1.15 → 0.1.16

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.
Files changed (80) hide show
  1. package/README.md +9 -1
  2. package/dist/api/camera-tool-api.js +108 -0
  3. package/dist/api/clips-tool-api.js +36 -0
  4. package/dist/api/create-camera-policy-tool-api.js +9 -0
  5. package/dist/api/create-tool-api.js +53 -0
  6. package/dist/api/entity-lookup-tool-api.js +37 -0
  7. package/dist/api/events-tool-api.js +320 -0
  8. package/dist/api/faces-tool-api.js +110 -0
  9. package/dist/api/get-entity-tool-api.js +176 -0
  10. package/dist/api/get-org-information-tool-api.js +9 -0
  11. package/dist/api/location-tool-api.js +9 -0
  12. package/dist/api/lpr-tool-api.js +68 -0
  13. package/dist/api/policy-alerts-tool-api.js +42 -0
  14. package/dist/api/reboot-cameras-tool-api.js +34 -0
  15. package/dist/api/report-tool-api.js +426 -0
  16. package/dist/api/time-tool-api.js +90 -0
  17. package/dist/api/update-tool-api.js +148 -0
  18. package/dist/createServer.js +7 -1
  19. package/dist/disabled-tools/endpoint-to-keys-tool.js +84 -0
  20. package/dist/disabled-tools/semantic-search-tool.js +90 -0
  21. package/dist/index.js +3 -5
  22. package/dist/logger.js +4 -3
  23. package/dist/network.js +42 -15
  24. package/dist/resources/routes.json.js +1 -1
  25. package/dist/services/embedding-service.js +153 -0
  26. package/dist/services/faiss-search-service.js +261 -0
  27. package/dist/tools/camera-tool.js +100 -0
  28. package/dist/tools/clips-tool.js +23 -37
  29. package/dist/tools/count-tool.js +25 -0
  30. package/dist/tools/create-camera-policy-tool.js +214 -0
  31. package/dist/tools/create-tool.js +25 -74
  32. package/dist/tools/entity-lookup-tool.js +35 -0
  33. package/dist/tools/events-tool.js +188 -92
  34. package/dist/tools/faces-tool.js +59 -132
  35. package/dist/tools/get-entity-tool.js +78 -0
  36. package/dist/tools/get-org-information-tool.js +18 -0
  37. package/dist/tools/location-tool.js +24 -31
  38. package/dist/tools/lpr-tool.js +71 -0
  39. package/dist/tools/policy-alerts-tool.js +32 -42
  40. package/dist/tools/reboot-cameras-tool.js +34 -0
  41. package/dist/tools/report-tool.js +190 -0
  42. package/dist/tools/time-conversion-tool.js +43 -0
  43. package/dist/tools/time-tool.js +17 -57
  44. package/dist/tools/update-tool.js +262 -0
  45. package/dist/transports/streamable-http.js +160 -57
  46. package/dist/{tools/devices/camera-tool/types.js → types/camera-tool-types.js} +13 -0
  47. package/dist/types/clips-tool-types.js +41 -0
  48. package/dist/types/create-camera-policy-tool-types.js +44 -0
  49. package/dist/types/create-tool-types.js +8 -0
  50. package/dist/types/deviceType.js +1 -0
  51. package/dist/types/endpoint-to-keys-tool-types.js +7 -0
  52. package/dist/types/entity-lookup-tool-types.js +70 -0
  53. package/dist/types/events-tools-types.js +257 -0
  54. package/dist/types/faces-tools-types.js +143 -0
  55. package/dist/types/get-entity-tool-types.js +25 -0
  56. package/dist/types/get-org-information-tool-types.js +3 -0
  57. package/dist/types/location-tool-types.js +11 -0
  58. package/dist/types/lpr-tool-types.js +97 -0
  59. package/dist/types/policy-alerts-tool-types.js +74 -0
  60. package/dist/types/reboot-cameras-tool-types.js +8 -0
  61. package/dist/types/report-tool-types.js +268 -0
  62. package/dist/types/schema-components.js +7093 -0
  63. package/dist/types/schema.js +1 -0
  64. package/dist/types/semantic-search-tool-types.js +5 -0
  65. package/dist/types/time-conversion-tool-types.js +8 -0
  66. package/dist/types/time-tool-types.js +11 -0
  67. package/dist/types/update-tool-types.js +186 -0
  68. package/dist/types/zod-schemas.js +21315 -0
  69. package/dist/types.js +17 -7
  70. package/dist/util.js +94 -2
  71. package/dist/utils/confirmation.js +1 -1
  72. package/dist/utils/reduce-output.js +35 -0
  73. package/dist/utils/remove-nulls.js +28 -0
  74. package/dist/utils/temp.js +8 -0
  75. package/dist/utils/timestampInput.js +12 -0
  76. package/package.json +23 -3
  77. package/dist/tools/devices/camera-tool/camera-tool.js +0 -218
  78. package/dist/tools/devices/get-entity-tool.js +0 -118
  79. package/dist/tools/get-org-information.js +0 -17
  80. package/dist/tools/reboot-cameras.js +0 -62
@@ -1,79 +1,30 @@
1
- import { z } from "zod";
2
- import { logger } from "../logger.js";
3
- import { postApi } from "../network.js";
4
- import { CreateVideoWallOptions } from "../types.js";
5
- import { addConfirmationParams, requireConfirmation } from "../utils/confirmation.js";
6
- async function createVideoWall(options, requestModifiers) {
7
- const body = {
8
- videoWall: {
9
- displayName: options?.displayName,
10
- deviceList: options?.deviceList,
11
- othersCanEdit: true,
12
- orgUuid: options?.orgUuid,
13
- shared: true,
14
- settings: {
15
- gridSize: { width: options?.settings.columnCount, height: options?.settings.columnCount },
16
- gridLayout: "1 2\n3 4",
17
- intervalSeconds: options?.settings.intervalSeconds || 5,
18
- },
19
- },
20
- };
21
- const response = await postApi("/camera/createVideoWall", body, requestModifiers);
22
- return response;
23
- }
24
- async function handleCreateVideoWallRequest(videoWallCreateOptions, requestModifiers) {
25
- let text = "Unable to create video wall!";
26
- logger.info("🔨 Creating video wall");
27
- if (!videoWallCreateOptions?.displayName) {
28
- text = JSON.stringify({
29
- needUserInput: true,
30
- commandForUser: "What should the name of the video wall be?",
31
- });
32
- }
33
- else if ((videoWallCreateOptions?.deviceList || []).length === 0) {
34
- text = JSON.stringify({
35
- needUserInput: true,
36
- commandForUser: "Which cameras would you like on this video wall?",
37
- });
1
+ import { requireConfirmation } from "../utils/confirmation.js";
2
+ import { handleCreateVideoWallRequest } from "../api/create-tool-api.js";
3
+ import { TOOL_ARGS } from "../types/create-tool-types.js";
4
+ const TOOL_NAME = "create-tool";
5
+ const TOOL_DESCRIPTION = "Tool for creating many entity types such as video walls.";
6
+ const TOOL_HANDLER = async (args, extra) => {
7
+ const { entityType, videoWallCreateOptions, confirmationId } = args;
8
+ const confirmation = requireConfirmation(confirmationId);
9
+ if (confirmation === true) {
10
+ switch (entityType) {
11
+ case "video-wall":
12
+ return await handleCreateVideoWallRequest(videoWallCreateOptions, extra._meta?.requestModifiers, extra.sessionId);
13
+ default:
14
+ }
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: "",
20
+ },
21
+ ],
22
+ };
38
23
  }
39
24
  else {
40
- logger.info("Creating video wall with options: ", JSON.stringify(videoWallCreateOptions));
41
- text = JSON.stringify(await createVideoWall(videoWallCreateOptions, requestModifiers));
25
+ return confirmation;
42
26
  }
43
- return Promise.resolve({
44
- content: [
45
- {
46
- type: "text",
47
- text,
48
- },
49
- ],
50
- });
51
- }
27
+ };
52
28
  export function createTool(server) {
53
- server.tool("create-tool", "Tool for creating many entity types such as video walls.", addConfirmationParams({
54
- entityType: z
55
- .enum(["video-wall"])
56
- .describe("The entity type to create. Example: video wall."),
57
- videoWallCreateOptions: CreateVideoWallOptions,
58
- }), async ({ entityType, videoWallCreateOptions, confirmationId }, extra) => {
59
- const confirmation = requireConfirmation(confirmationId);
60
- if (confirmation === true) {
61
- switch (entityType) {
62
- case "video-wall":
63
- return await handleCreateVideoWallRequest(videoWallCreateOptions, extra._meta?.requestModifiers);
64
- default:
65
- }
66
- return {
67
- content: [
68
- {
69
- type: "text",
70
- text: "",
71
- },
72
- ],
73
- };
74
- }
75
- else {
76
- return confirmation;
77
- }
78
- });
29
+ server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_ARGS, TOOL_HANDLER);
79
30
  }
@@ -0,0 +1,35 @@
1
+ import { getAllEntities } from "../api/entity-lookup-tool-api.js";
2
+ import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/entity-lookup-tool-types.js";
3
+ import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
4
+ const TOOL_NAME = "entity-lookup-tool";
5
+ const TOOL_DESCRIPTION = `
6
+ Retrieves specific entities (or devices) by their UUIDs.
7
+ Takes a list of device UUIDs and returns the device information for those specific devices.
8
+ The return structure is a JSON object that contains the states of the requested entities.
9
+ This data is exact. Only devices with matching UUIDs will be returned.
10
+ `;
11
+ const TOOL_HANDLER = async (args, extra) => {
12
+ const { deviceUuids, timeZone, tempUnit } = args;
13
+ const { requestModifiers, sessionId } = extractFromToolExtra(extra);
14
+ try {
15
+ const result = await getAllEntities(deviceUuids, timeZone, tempUnit, requestModifiers, sessionId);
16
+ return createToolStructuredContent(result);
17
+ }
18
+ catch (error) {
19
+ if (error instanceof Error) {
20
+ return createToolStructuredContent({
21
+ error: error.message,
22
+ });
23
+ }
24
+ return createToolStructuredContent({
25
+ error: "Unknown error",
26
+ });
27
+ }
28
+ };
29
+ export function createTool(server) {
30
+ server.registerTool(TOOL_NAME, {
31
+ description: TOOL_DESCRIPTION,
32
+ inputSchema: TOOL_ARGS,
33
+ outputSchema: OUTPUT_SCHEMA.shape,
34
+ }, TOOL_HANDLER);
35
+ }
@@ -1,100 +1,196 @@
1
- import { z } from "zod";
2
- import { FIVE_SECONDS_MS, THREE_HOURS_MS } from "../constants.js";
3
- import { postApi } from "../network.js";
4
- async function getFaceEvents(_locationUuid, requestModifiers) {
5
- const nowMs = Date.now();
6
- const rangeStartMs = nowMs - THREE_HOURS_MS;
7
- const rangeEndMs = nowMs - FIVE_SECONDS_MS;
8
- const body = {
9
- pageRequest: {
10
- lastEvaluatedKey: undefined,
11
- maxPageSize: 75,
12
- },
13
- searchFilter: {
14
- deviceUuids: [],
15
- faceNames: [],
16
- labels: [],
17
- locationUuids: [],
18
- personUuids: [],
19
- timestampFilter: {
20
- rangeStart: rangeStartMs,
21
- rangeEnd: rangeEndMs,
22
- },
23
- },
24
- };
25
- const response = await postApi("/faceRecognition/faceEvent/findFaceEventsByOrg", body, requestModifiers).then(response => {
26
- return {
27
- faceEvents: (response.faceEvents || []).map((event) => ({
28
- ...event,
29
- eventTimestamp: new Date(event.eventTimestamp).toString(),
30
- })),
31
- };
32
- });
33
- return response;
34
- }
35
- async function getAccessControlEvents(doorUuid, requestModifiers) {
36
- const body = {
37
- limit: 50,
38
- accessControlledDoorUuid: doorUuid,
39
- };
40
- const response = await postApi("/component/findComponentEventsByAccessControlledDoor", body, requestModifiers).then(response => ({
41
- componentEvents: (response.componentEvents || []).map((event) => ({
42
- ...event,
43
- timestamp: new Date(event.timestampMs).toString(),
44
- })),
45
- }));
46
- return response;
47
- }
48
- export function createTool(server) {
49
- server.tool("events-tool", "event data for certain types of information like faces, license plates, and access-control events", {
50
- eventType: z.enum(["faces", "people", "access-control"]),
51
- locationUuid: z.optional(z.string()),
52
- accessControlledDoorUuid: z.optional(z.string()),
53
- }, async ({ eventType, locationUuid, accessControlledDoorUuid }, extra) => {
54
- if (eventType === "faces" || eventType === "people") {
55
- const response = await getFaceEvents(locationUuid, extra._meta?.requestModifiers);
56
- return {
57
- content: [
58
- {
59
- type: "text",
60
- text: JSON.stringify(response),
61
- },
62
- ],
63
- };
1
+ import { getAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getHumanMotionEvents, } from "../api/events-tool-api.js";
2
+ import { EventsToolRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/events-tools-types.js";
3
+ import { createToolStructuredContent } from "../util.js";
4
+ import { getLogger } from "../logger.js";
5
+ import { TempUnit } from "../utils/temp.js";
6
+ const logger = getLogger("events-tool");
7
+ const TOOL_NAME = "events-tool";
8
+ // "faces" | "people" | "human" | "access-control"
9
+ const TOOL_DESCRIPTION = `
10
+ This tool interacts with the Rhombus events system to retrieve information about various types of events within the system. It has 5 modes of operation, determined by the "eventType" parameter: access-control, environmental-gateway, climate-sensor, component-events, and camera
11
+
12
+ This tool should should be used any time someone is asking for specifics or reports for access control related events like unlocks, badge ins, credentials, arrivals etc., environmental gateway events, climate sensor events, camera motion events, or any other component events.
13
+
14
+ For maximum flexibility, use eventType "component-events" which allows querying any combination of event types (doorbell pushes, badge scans, door state changes, button presses, etc.) for a location.
15
+
16
+ This tool retrieves a list of events captured by the access control door system pertaining to arrivals, badge ins, credentials received, etc.
17
+
18
+ This tool can return a lot of data. Please make sure the time range provided is not too large.
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.
23
+
24
+ The tool returns a JSON object with access control events data.
25
+
26
+ When eventType is "environmental-gateway":
27
+
28
+ This tool retrieves environmental gateway events for a specific environmental gateway device within a time range. The data returned will have a timestamp that is in
29
+ the timezone of the **device**, not necessarily UTC time.
30
+
31
+ This tool takes 3 arguments:
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.
35
+
36
+ The tool returns a JSON object with environmental gateway events data.
37
+
38
+ When eventType is "climate-sensor":
39
+
40
+ This tool retrieves climate sensor events for a specific climate sensor within a time range. The data returned will have a timestamp that is in
41
+ the timezone of the **sensor**, not necessarily UTC time.
42
+
43
+ This tool takes 4 arguments:
44
+ * **sensorUuid (string):** The unique identifier for the climate sensor.
45
+ * **startTime (string):** The timestamp (in ISO 8601 format) representing the start time of events.
46
+ * **endTime (string):** The timestamp (in ISO 8601 format) representing the end time of events.
47
+ * **limit (number, optional):** Maximum number of climate events to return. Default is 1000.
48
+
49
+ The tool returns a JSON object with climate sensor events data.
50
+
51
+ When eventType is "component-events":
52
+
53
+ This tool retrieves ALL types of component events for a specific location within a time range. This is the most flexible option and allows filtering by specific event types. The data returned will have a timestamp that is in the timezone of the **location**, not necessarily UTC time.
54
+
55
+ This tool takes 4 arguments:
56
+ * **locationUuid (string):** The unique identifier for the location.
57
+ * **componentEventTypes (array of strings, optional):** Array of event types to filter by. If empty or not provided, returns all event types.
58
+ * **startTime (string):** The timestamp (in ISO 8601 format) representing the start time of events.
59
+ * **endTime (string):** The timestamp (in ISO 8601 format) representing the end time of events.
60
+
61
+ Valid event types include:
62
+ * **DoorbellEvent:** Doorbell button press events
63
+ * **CredentialReceivedEvent:** Badge/credential scans (NFC, BLE_WAVE, REMOTE unlocks)
64
+ * **DoorStateChangeEvent:** Door state changes (locked/unlocked)
65
+ * **ButtonEvent:** Generic button press events
66
+ * **PanicButtonEvent:** Panic/emergency button activations
67
+ * **DoorReaderStateChangeEvent:** Changes in door reader state
68
+ * **DoorRelayStateChangeEvent:** Changes in door relay state
69
+ * **AccessControlUnitTamperEvent:** Tamper detection events
70
+ * **AccessControlUnitBatteryStateChangeEvent:** Battery state changes
71
+ * **WaveToUnlockIntentExpiredEvent:** Wave-to-unlock timeout events
72
+ * **DoorAuthFirstInStateEvent:** First-in authentication state events
73
+ * **DoorScheduleFirstInStateEvent:** First-in schedule state events
74
+ * And more...
75
+
76
+ When eventType is "camera":
77
+
78
+ This tool retrieves human motion events detected by a specific camera within a time range. The data returned will have timestamps in milliseconds.
79
+
80
+ This tool takes 3 arguments:
81
+ * **cameraUuid (string):** The unique identifier for the camera.
82
+ * **startTime (string):** The timestamp (in ISO 8601 format) representing the start time of events.
83
+ * **duration (number):** Duration in seconds to search for human motion events. Default is 3600 (1 hour).
84
+
85
+ The tool returns a JSON object with camera events data.
86
+ `;
87
+ const TOOL_HANDLER = async (args, extra) => {
88
+ const { eventType, accessControlledDoorUuids, deviceUuid, sensorUuid, locationUuid, componentEventTypes, startTime, endTime, limit, timeZone, tempUnit, cameraUuid, duration, } = args;
89
+ logger.debug(`eventType: ${eventType}`);
90
+ switch (eventType) {
91
+ case "access-control": {
92
+ if (!accessControlledDoorUuids || accessControlledDoorUuids.length === 0) {
93
+ const result = {
94
+ needUserInput: true,
95
+ commandForUser: "Which door are you asking about?",
96
+ };
97
+ return createToolStructuredContent(result);
98
+ }
99
+ else {
100
+ const events = await getAccessControlEvents(accessControlledDoorUuids, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId);
101
+ const result = {
102
+ eventType: "access-control",
103
+ accessControlEvents: events,
104
+ };
105
+ return createToolStructuredContent(result);
106
+ }
64
107
  }
65
- if (eventType === "access-control") {
66
- if (!accessControlledDoorUuid) {
67
- return {
68
- content: [
69
- {
70
- type: "text",
71
- text: JSON.stringify({
72
- needUserInput: true,
73
- commandForUser: "Which door are you asking about?",
74
- }),
75
- },
76
- ],
108
+ case "environmental-gateway": {
109
+ if (!deviceUuid) {
110
+ const result = {
111
+ needUserInput: true,
112
+ commandForUser: "Which environmental gateway device are you asking about?",
77
113
  };
114
+ return createToolStructuredContent(result);
78
115
  }
79
116
  else {
80
- const events = await getAccessControlEvents(accessControlledDoorUuid, extra._meta?.requestModifiers);
81
- return {
82
- content: [
83
- {
84
- type: "text",
85
- text: JSON.stringify(events),
86
- },
87
- ],
117
+ 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
+ const result = {
119
+ eventType: "environmental-gateway",
120
+ environmentalGatewayEvents: events,
88
121
  };
122
+ return createToolStructuredContent(result);
89
123
  }
90
124
  }
91
- return {
92
- content: [
93
- {
94
- type: "text",
95
- text: JSON.stringify({}),
96
- },
97
- ],
98
- };
99
- });
125
+ case "climate-sensor": {
126
+ if (!sensorUuid) {
127
+ const result = {
128
+ needUserInput: true,
129
+ commandForUser: "Which climate sensor are you asking about?",
130
+ };
131
+ return createToolStructuredContent(result);
132
+ }
133
+ else {
134
+ 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
+ const result = {
136
+ eventType: "climate-sensor",
137
+ climateSensorEvents: events,
138
+ };
139
+ return createToolStructuredContent(result);
140
+ }
141
+ }
142
+ case "component-events": {
143
+ if (!locationUuid) {
144
+ const result = {
145
+ needUserInput: true,
146
+ commandForUser: "Which location are you asking about?",
147
+ };
148
+ return createToolStructuredContent(result);
149
+ }
150
+ else {
151
+ 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
+ const result = {
153
+ eventType: "component-events",
154
+ componentEvents: events,
155
+ };
156
+ return createToolStructuredContent(result);
157
+ }
158
+ }
159
+ case EventsToolRequestType.CAMERA: {
160
+ if (!cameraUuid) {
161
+ const result = {
162
+ needUserInput: true,
163
+ commandForUser: "Which camera are you asking about?",
164
+ };
165
+ return createToolStructuredContent(result);
166
+ }
167
+ else {
168
+ const events = await getHumanMotionEvents(cameraUuid, duration ?? 3600, // Default to 1 hour if not provided
169
+ startTime ? new Date(startTime).getTime() : Date.now() - 3600000, // Default to 1 hour ago if not provided
170
+ extra._meta?.requestModifiers, extra.sessionId);
171
+ return createToolStructuredContent({
172
+ eventType: "camera",
173
+ cameraEvents: events.uniqueHumanEvents,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ // This should not happen, but return empty result if eventType is unknown
179
+ const result = {};
180
+ return {
181
+ content: [
182
+ {
183
+ type: "text",
184
+ text: JSON.stringify(result),
185
+ },
186
+ ],
187
+ structuredContent: result,
188
+ };
189
+ };
190
+ export function createTool(server) {
191
+ server.registerTool(TOOL_NAME, {
192
+ description: TOOL_DESCRIPTION,
193
+ inputSchema: TOOL_ARGS,
194
+ outputSchema: OUTPUT_SCHEMA.shape,
195
+ }, TOOL_HANDLER);
100
196
  }
@@ -1,139 +1,66 @@
1
- import { z } from "zod";
2
- import { postApi } from "../network.js";
3
- var RequestType;
4
- (function (RequestType) {
5
- RequestType["GET_FACE_EVENTS"] = "get-face-events";
6
- RequestType["GET_REGISTERED_FACES"] = "get-registered-faces";
7
- })(RequestType || (RequestType = {}));
8
- // Zod Schema for 'get-registered-faces' arguments (no specific args)
9
- const GetRegisteredFacesArgsSchema = z.object({
10
- requestType: z.literal(RequestType.GET_REGISTERED_FACES),
11
- });
12
- // --- GetFaceEventsArgs Schema ---
13
- // All 'get-face-events' specific arguments are directly in this object,
14
- // marked as nullable since they are conditional based on requestType.
15
- const GetFaceEventsArgs = z.object({
16
- // Arguments for 'get-face-events' (flattened from original searchFilter)
17
- deviceUuids: z
18
- .array(z.string())
19
- .optional()
20
- .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'."),
21
- faceNameContains: z
22
- .string()
23
- .optional()
24
- .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'."),
25
- faceNames: z
26
- .array(z.string())
27
- .optional()
28
- .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'."),
29
- hasEmbedding: z
30
- .boolean()
31
- .optional()
32
- .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'."),
33
- hasName: z
34
- .boolean()
35
- .optional()
36
- .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'."),
37
- labels: z
38
- .array(z.string())
39
- .optional()
40
- .describe("Optional filter by a set of labels associated with the face event. Applicable when requestType is 'get-face-events'."),
41
- locationUuids: z
42
- .array(z.string())
43
- .optional()
44
- .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'."),
45
- personUuids: z
46
- .array(z.string())
47
- .optional()
48
- .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'."),
49
- // Arguments for 'get-face-events' (flattened from original timestampFilter)
50
- rangeEnd: z
51
- .string()
52
- .optional()
53
- .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."),
54
- rangeStart: z
55
- .string()
56
- .optional()
57
- .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."),
58
- // No specific arguments for 'get-registered-faces' beyond requestType itself.
59
- });
60
- // --- Extracted PostAPI Calls into Functions ---
61
- async function getFaceEvents(args, requestModifiers) {
62
- // Directly pass the args object as requested by the user
63
- return await postApi("/faceRecognition/faceEvent/findFaceEventsByOrg", args, requestModifiers);
64
- }
65
- async function getRegisteredFaces(args, requestModifiers) {
66
- // No specific arguments for this API call, so send an empty object.
67
- return await postApi("/faceRecognition/person/findPeopleByOrg", {}, requestModifiers);
68
- }
69
- // --- Main Tool Definition ---
70
- export function createTool(server) {
71
- server.tool("faces-tool", `
72
- 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:
1
+ import { getFaceEvents, getRegisteredFaces } from "../api/faces-tool-api.js";
2
+ import { OUTPUT_SCHEMA, RequestType, TOOL_ARGS, } from "../types/faces-tools-types.js";
3
+ import { extractFromToolExtra } from "../util.js";
4
+ const TOOL_NAME = "faces-tool";
5
+ // TODO: deviceUuids filter was removed, see TOOL_ARGS for reason
6
+ const TOOL_DESCRIPTION = `
7
+ This tool interacts with the Rhombus face recognition system to retrieve information about face sightings and registered faces. It has two primary modes of operation, determined by the "requestType" parameter:
73
8
 
74
- If the requestType is "get-face-events":
9
+ It should only be used if someone is specifically asking about faces. If they are asking about people or humans, you should use the events-tool with eventType "people" instead as this tool is for face sightings only.
75
10
 
76
- 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.
77
-
78
- 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).
79
-
80
- The tool returns a JSON object with the following structure and important fields:
81
- * **lastEvaluatedKey (string | null):** A key for pagination; if not null, it can be used to retrieve the next page of results.
82
- * **faceEvents (array of objects | null):** An array where each object represents a single face event. Each face event object contains the following important fields:
83
- * **uuid (string):** The unique identifier for this specific face event.
84
- * **eventTimestamp (int64):** The timestamp (in milliseconds since epoch) when the face event occurred.
85
- * **faceName (string | null):** The name of the person that matched the face image, if a match was found.
86
- * **personUuid (string | null):** The UUID of the person that matched the face image, if a match was found.
87
- * **deviceUuid (string):** The UUID of the device (e.g., camera) where the face event was detected.
88
- * **locationUuid (string | null):** The UUID of the location where the face event occurred.
89
- * **orgUuid (string | null):** The UUID of the organization associated with the event.
90
- * **detectionConfidence (float | null):** A confidence value (between 0.0 and 1.0) indicating the likelihood that the detected image is a face.
91
- * **thumbnailS3Key (string | null):** The S3 key for accessing the thumbnail image of the detected face.
92
- * **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:
93
- * **confidence (float | null):** The match confidence level, expressed as a value in the range [0,1].
94
- * **faceId (string | null):** The face ID of the matched person.
95
- * **name (string | null):** The name of the matched person.
96
- * **uuid (string | null):** The UUID of the matched person.
11
+ 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". Can be used for reporting, to generate a report on who was seen by the camera system.
13
+ - This tool retrieves detailed face events 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.
14
+ - 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
+ - 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.
97
16
 
98
17
  If the requestType is "get-registered-faces":
99
-
100
- 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.
101
-
102
- This tool takes no arguments.
103
-
104
- The tool returns a JSON object with the following structure and important fields:
105
- * **people (array of objects | null):** An array where each object represents a registered person. Each person object contains the following important fields:
106
- * **uuid (string):** The unique identifier for the registered person.
107
- * **name (string | null):** The name associated with the registered person.
108
- * **createdOn (date-time | null):** The date and time when the person was registered.
109
- * **updatedOn (date-time | null):** The date and time when the person's information was last updated.
110
- * **orgUuid (string | null):** The UUID of the organization to which the person belongs.
111
-
112
- `,
113
- // FacesToolArgs directly passed here
114
- {
115
- requestType: z.nativeEnum(RequestType),
116
- args: z.union([
117
- z.object({
118
- args: GetFaceEventsArgs,
119
- }),
120
- z.object({
121
- requestType: z.literal(RequestType.GET_REGISTERED_FACES),
122
- args: GetRegisteredFacesArgsSchema,
123
- }),
124
- ]),
125
- }, async ({ requestType, args }, extra) => {
126
- let ret;
127
- if (requestType === "get-face-events") {
128
- // Pass the args directly, as requested
129
- ret = await getFaceEvents(args, extra._meta?.requestModifiers);
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
+ - this returns ALL people registered in the system, regardless of the provided timestampFilter.
20
+ - This is useful to call before calling "get-face-events" to get a list of all people that have been seen by the camera system, and then you can use personUuids or the name *in the system* to filter face events.
21
+ `;
22
+ const TOOL_HANDLER = async (args, extra) => {
23
+ const { requestModifiers, sessionId } = extractFromToolExtra(extra);
24
+ let ret = {
25
+ requestType: args.requestType,
26
+ };
27
+ if (args.requestType === RequestType.GET_FACE_EVENTS) {
28
+ const response = await getFaceEvents(args.faceEventFilter, args.timeZone, requestModifiers, sessionId);
29
+ ret = {
30
+ requestType: RequestType.GET_FACE_EVENTS,
31
+ getFaceEventsResponse: response,
32
+ };
33
+ }
34
+ else if (args.requestType === RequestType.GET_REGISTERED_FACES) {
35
+ const response = await getRegisteredFaces(args, requestModifiers, sessionId);
36
+ if (response.people) {
37
+ ret = {
38
+ requestType: RequestType.GET_REGISTERED_FACES,
39
+ getSavedFacesResponse: response.people.map(p => ({
40
+ createdOn: p.createdOn ? parseInt(p.createdOn, 10) : undefined,
41
+ name: p.name ?? undefined,
42
+ orgUuid: p.orgUuid ?? undefined,
43
+ updatedOn: p.updatedOn ? parseInt(p.updatedOn, 10) : undefined,
44
+ uuid: p.uuid ?? undefined,
45
+ })),
46
+ };
130
47
  }
131
- else if (requestType === "get-registered-faces") {
132
- // Pass the args (will effectively be an empty object for this call)
133
- ret = await getRegisteredFaces(args, extra._meta?.requestModifiers);
48
+ else {
49
+ ret = {
50
+ requestType: RequestType.GET_REGISTERED_FACES,
51
+ error: String(response.error),
52
+ };
134
53
  }
135
- return {
136
- content: [{ type: "text", text: JSON.stringify(ret) }],
137
- };
138
- });
54
+ }
55
+ return {
56
+ content: [{ type: "text", text: JSON.stringify(ret) }],
57
+ structuredContent: ret,
58
+ };
59
+ };
60
+ export function createTool(server) {
61
+ server.registerTool(TOOL_NAME, {
62
+ description: TOOL_DESCRIPTION,
63
+ inputSchema: TOOL_ARGS,
64
+ outputSchema: OUTPUT_SCHEMA.shape,
65
+ }, TOOL_HANDLER);
139
66
  }